Skip to content
Back to skills

Security And Hardening

ASecurity

Use when handling sensitive data, authentication, network communication, or before shipping to the Play Store. Three-tier framework (Always Do, Ask First, Never Do) with Android-specific security patterns, plus data privacy and compliance (GDPR/CCPA, Play Data safety, account deletion).

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 4, 2026
securityjavascriptrustgojavakotlingitapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 4, 2026

npx -y skills add GuillemRoca/agent-skills-android --skill security-and-hardening --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Security And Hardening?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Security And Hardening
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/guillemroca-security-and-hardening/badge)](https://www.skillsdirectory.com/skills/guillemroca-security-and-hardening)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: security-and-hardening
description: >-
  Use when handling sensitive data, authentication, network communication,
  or before shipping to the Play Store. Three-tier framework (Always Do,
  Ask First, Never Do) with Android-specific security patterns, plus data
  privacy and compliance (GDPR/CCPA, Play Data safety, account deletion).
---

# Security and Hardening

## Overview

Security is a development constraint, not an afterthought. This skill provides a three-tier framework — Always Do, Ask First, Never Do — covering the OWASP Mobile Top 10, Android-specific attack vectors, and hardening patterns for production apps.

## When to Use

- Handling user credentials, tokens, or personal data
- Implementing network communication
- Before shipping any release to the Play Store
- Reviewing code that touches authentication or authorization
- Setting up ProGuard/R8 rules
- Implementing WebView features
- Collecting personal data, adding analytics/ads SDKs, or answering privacy requirements (GDPR, CCPA, Play Data safety)

**Skip when:** Changes are purely cosmetic with no data or network impact.

## Core Process: Three-Tier Framework

### Always Do

1. **Secure data storage.** Jetpack Security Crypto (`EncryptedSharedPreferences`/`MasterKey`) is deprecated and unmaintained — do not add it to new code. Encrypt with a key held in the Android Keystore and persist the ciphertext (DataStore or a file):

```kotlin
// Key lives in the Android Keystore — never leaves secure hardware
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(
    KeyGenParameterSpec.Builder("auth_token_key", KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build()
)
val secretKey = keyGenerator.generateKey()

// Encrypt, then persist iv + ciphertext (e.g. in Proto DataStore)
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply { init(Cipher.ENCRYPT_MODE, secretKey) }
val encrypted = cipher.iv + cipher.doFinal(token.toByteArray())
```

Existing apps already on `EncryptedSharedPreferences` can keep it (the format is stable), but plan a migration and never store new categories of secrets with it.

2. **Network Security Config:**

```xml
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <!-- Enforce HTTPS for all connections -->
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>

    <!-- Certificate pinning for your API -->
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2025-12-31">
            <pin digest="SHA-256">base64EncodedPin=</pin>
            <!-- Backup pin -->
            <pin digest="SHA-256">base64EncodedBackupPin=</pin>
        </pin-set>
    </domain-config>
</network-security-config>
```

```xml
<!-- AndroidManifest.xml -->
<application android:networkSecurityConfig="@xml/network_security_config">
```

Targeting API 37 (Android 17) also changes the network/auth surface: LAN access requires
the `ACCESS_LOCAL_NETWORK` permission, standard SMS OTPs are delayed 3 hours (use the SMS
Retriever API or SMS User Consent instead of reading raw SMS), and Encrypted Client Hello +
Certificate Transparency are on by default for TLS — verify pinning still works.

3. **Input validation:**

```kotlin
// Validate all external input
fun processDeepLink(uri: Uri): DeepLinkResult {
    val taskId = uri.getQueryParameter("taskId")
        ?: return DeepLinkResult.Invalid("Missing taskId")

    // Validate format
    if (!taskId.matches(Regex("^[a-zA-Z0-9-]{1,36}$"))) {
        return DeepLinkResult.Invalid("Invalid taskId format")
    }

    return DeepLinkResult.Valid(taskId)
}
```

4. **Intent validation for exported components:**

```kotlin
// Validate intents for exported Activities/Receivers
class TaskDeepLinkActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val intent = intent ?: run { finish(); return }

        // Validate the source and data
        val taskId = intent.data?.getQueryParameter("taskId")
        if (taskId == null || !isValidTaskId(taskId)) {
            finish()
            return
        }
        // Process valid deep link
    }
}
```

   Deeper audits: `android-skills:android-intent-security` and `android-skills:android-permissions-security` (optional Google companion plugin, see README).

5. **Secrets management:**

```properties
# local.properties (NEVER committed to git)
API_KEY=your_secret_key_here
```

```kotlin
// build.gradle.kts
val localProperties = Properties().apply {
    val file = rootProject.file("local.properties")
    if (file.exists()) load(file.inputStream())
}

android {
    defaultConfig {
        buildConfigField("String", "API_KEY",
            "\"${localProperties.getProperty("API_KEY", "")}\"")
    }
}
```

```gitignore
# .gitignore
local.properties
*.jks
*.keystore
```

6. **Remove debug code for release:**

```kotlin
// build.gradle.kts — strip Log calls in release
buildTypes {
    release {
        isMinifyEnabled = true
        proguardFiles(
            getDefaultProguardFile("proguard-android-optimize.txt"),
            "proguard-rules.pro"
        )
    }
}

// proguard-rules.pro
-assumenosideeffects class android.util.Log {
    public static int d(...);
    public static int v(...);
}
```

### Ask First

7. **Changes that require review:**
   - Modifying authentication or authorization logic
   - Adding new exported components (Activities, Receivers, Providers)
   - Changing certificate pinning configuration
   - Adding new permissions to AndroidManifest
   - Integrating new third-party SDKs
   - Implementing WebView with JavaScript enabled
   - Modifying ProGuard/R8 rules
   - Changing data encryption or storage approach

### Never Do

8. **Absolute prohibitions:**

| Never | Why |
|-------|-----|
| Commit secrets to git | Secrets in git history are permanent, even after removal |
| Log sensitive data (`Log.d` with tokens) | Logcat is accessible to other apps on rooted devices |
| Trust client-side validation alone | All client validation can be bypassed |
| Use `MODE_WORLD_READABLE` | Any app can read your files |
| Enable `cleartext` traffic in production | Data interceptable on network |
| Store passwords in plain text | Use EncryptedSharedPreferences or Android Keystore |
| Use `WebView.setJavaScriptEnabled(true)` without sanitizing | XSS attacks via injected content |
| Export components without `android:permission` | Any app can invoke them |
| Disable certificate verification | Man-in-the-middle attacks |
| Use `FLAG_SECURE` to prevent screenshots AND log sensitive data | Inconsistent security posture |

### WebView Security

9. **If you must use WebView:**

```kotlin
webView.settings.apply {
    javaScriptEnabled = true // Only if necessary
    allowFileAccess = false
    allowContentAccess = false
    domStorageEnabled = true

    // Restrict to your domain
    setSupportMultipleWindows(false)
}

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
        val url = request.url
        // Only allow your trusted domains
        return url.host != "trusted.example.com"
    }
}

// Never add JavaScript interfaces that expose sensitive operations
// webView.addJavascriptInterface(dangerousObject, "Android") // DON'T
```

### Data Privacy & Compliance

10. **Hold less, for less time.** Security asks "can an attacker read it?"; privacy asks "should we hold it at all, and for how long?" Data never collected cannot be breached, mis-declared, or missed in a deletion. Classify every field as you add it:

| Class | Android examples | Handling |
|-------|------------------|----------|
| **Non-personal** | Aggregate counts, app version, crash-free rate | Normal handling |
| **Personal (PII)** | Name, email, IP, user ID, advertising ID, Android ID / other device IDs | Minimize, access-control, include in export/delete, declare in Data safety |
| **Sensitive** | Precise location, health, biometrics, contacts, photos/media, financial data, anything about minors | Explicit basis or consent, Keystore-backed encryption, never in cloud backup or logs |

**Operating rules:**
- **Collect against a stated purpose.** "Might be useful later" is breach scope, not a purpose. Don't log PII — `observability-and-instrumentation` covers log hygiene.
- **Minimize permissions.** Prefer the Photo Picker (`ActivityResultContracts.PickVisualMedia`) over `READ_MEDIA_IMAGES`, `ACCESS_COARSE_LOCATION` over `ACCESS_FINE_LOCATION`, and system pickers/intents over broad read permissions. Every permission you don't hold is data you can't leak.
- **Gate SDKs on consent.** Analytics, ads, and attribution SDKs must not start collecting in `Application.onCreate` before the user has chosen. Default collection off, then enable from the consent result:

```kotlin
// AndroidManifest.xml: <meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
fun applyConsent(analytics: FirebaseAnalytics, granted: Boolean) {
    val status = if (granted) ConsentStatus.GRANTED else ConsentStatus.DENIED
    analytics.setConsent(mapOf(ConsentType.ANALYTICS_STORAGE to status, ConsentType.AD_STORAGE to status))
    analytics.setAnalyticsCollectionEnabled(granted)
}
```

- **Declare what you actually ship.** The Play Data safety form must match real collection, *including every third-party SDK* (analytics, ads, crash reporting, attribution). Check each SDK's own data-disclosure docs and re-check the form whenever an SDK is added or upgraded.
- **Account deletion is a Play requirement.** Apps that let users create an account must offer deletion both in-app and via a web link listed in Play Console, and deleting the account must delete (or anonymize) its associated data — not just flip an `isDeleted` flag.
- **Set retention and make deletion reach every copy.** On sign-out or account deletion, clear Room tables, DataStore, files and caches, cancel WorkManager jobs whose input data carries personal payloads, and trigger server-side and analytics-vendor deletion. Exclude tokens, PII, and anything bound to a Keystore key (undecryptable after restore anyway) from Auto Backup — `android:dataExtractionRules` for API 31+, `android:fullBackupContent` for API 30 and below. Set both if `minSdk` < 31.
- **Design data-subject rights into the schema.** GDPR/CCPA export, correction, and deletion are engineering features: key personal data by user ID so it is *findable* and *erasable*, not smeared across denormalized tables, blobs, and logs.
- **Make policy configurable by region.** Consent requirements and residency rules differ by user location — keep them behind a config boundary rather than hardcoding one jurisdiction.

### OWASP Mobile Top 10 Quick Reference

| # | Risk | Android Mitigation |
|---|------|-------------------|
| M1 | Improper Credential Usage | Android Keystore, EncryptedSharedPreferences |
| M2 | Inadequate Supply Chain Security | Dependency verification, version pinning |
| M3 | Insecure Authentication | BiometricPrompt, OAuth2 with PKCE |
| M4 | Insufficient Input/Output Validation | Validate deep links, intents, API responses |
| M5 | Insecure Communication | Network Security Config, cert pinning |
| M6 | Inadequate Privacy Controls | Minimize data collection, respect permissions |
| M7 | Insufficient Binary Protections | R8/ProGuard, tamper detection |
| M8 | Security Misconfiguration | Review manifest, disable debuggable in release |
| M9 | Insecure Data Storage | EncryptedSharedPreferences, Room with encryption |
| M10 | Insufficient Cryptography | Android Keystore for key management |

## Common Rationalizations

| Shortcut | Why It Fails |
|----------|-------------|
| "It's just a debug key, I'll change it later" | Debug keys committed to git stay in history forever. |
| "Our API already validates input" | Client validation improves UX; server validation prevents exploits. Both needed. |
| "We don't need cert pinning for v1" | v1 is when MITM attacks are most damaging — no monitoring to detect them. |
| "ProGuard breaks too many things" | Configure it properly with `@Keep` annotations. The security cost of skipping is higher. |
| "Collect it now, we might need it later" | Data you don't hold can't be breached or mis-declared. "Might need it" is breach scope, not a purpose. |
| "The SDK handles its own privacy" | You declare its collection in Data safety and you answer for it. Gate it on consent and read its disclosure docs. |
| "Delete account = mark the user deleted" | Play requires the associated data to be deleted. Local DBs, backups, queued work, and vendor copies all hold it. |

## Red Flags

- Secrets (API keys, tokens) in source code
- `android:debuggable="true"` in release manifest
- `clearTextTrafficPermitted="true"` in production
- No Network Security Config
- `Log.d` with user data or tokens
- `MODE_WORLD_READABLE` or `MODE_WORLD_WRITEABLE`
- Exported components without permission checks
- WebView with unrestricted JavaScript
- No ProGuard/R8 in release builds
- Disabled certificate verification
- Analytics/ads SDK initialized in `Application.onCreate` before consent is captured
- New SDK or permission added without a Data safety form update
- Account deletion that only flips a flag, or no web deletion link
- `READ_MEDIA_IMAGES` / `ACCESS_FINE_LOCATION` where the Photo Picker / coarse location would do
- Auth tokens or PII included in Auto Backup (no `dataExtractionRules` / `fullBackupContent`)

## Verification

- [ ] No secrets in source code (grep for API keys, tokens, passwords)
- [ ] Network Security Config present and enforces HTTPS
- [ ] Certificate pinning for production API
- [ ] EncryptedSharedPreferences for sensitive data
- [ ] Input validated at all system boundaries
- [ ] Exported components have permission checks
- [ ] ProGuard/R8 enabled for release builds
- [ ] Debug logging stripped in release (ProGuard rules)
- [ ] `local.properties` in `.gitignore`
- [ ] `android:debuggable` not set (defaults to false for release)
- [ ] WebView (if used) restricts domains and JavaScript exposure
- [ ] Every personal-data field classified and tied to a stated purpose
- [ ] Analytics/ads SDKs collect nothing before consent (verify with a network inspector on a fresh install)
- [ ] Data safety form matches actual collection, including third-party SDKs
- [ ] Account deletion works in-app and via web link, and removes local, backup, queued, server, and vendor copies
- [ ] Backup rules exclude tokens/PII for both API 31+ and API 30-and-below paths

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…