---
name: plan-capacitor-hardening
description: >
  Plan-only Capacitor/Ionic native-layer security audit: WebView, token
  storage, deep links/OAuth, cleartext traffic, exported activities. Use
  when "is my Capacitor app secure?", "harden my hybrid app", or
  pre-store hardening. Store readiness → plan-mobile-readiness.
license: MIT
---

# Capacitor Native-Layer Hardening Audit + Remediation Plan

**Degree of freedom: HIGH** — native-layer inventory and plan. Stay
**plan-only**. No config, manifest, or code edits until approved.

## This skill vs neighbors

| Skill | Owns |
|---|---|
| **plan-capacitor-hardening** (this) | Native-layer / WebView security plan |
| `plan-mobile-readiness` | Store submission mechanics |
| `enhance-capacitor-ui` | Hybrid shell UI |

## How to reason (every plan item)

1. **Propose** — config, storage, deep-link, or WebView change
2. **Risk** — what a extracted APK or hijacked scheme actually exposes
3. **Keep-working** — native controls that already hold in production
4. **Phase** — prod-config leaks → tokens → auth → WebView → OTA (do not execute)

## Worked example

> **Propose:** `server.cleartext` and `webContentsDebuggingEnabled` false in prod; move auth tokens off Preferences onto Keychain/Keystore.
> **Risk:** release APK allows remote WebView inspect + plaintext token backup exfil.
> **Keep-working:** iOS ATS already blocks cleartext.
> **Phase:** Phase 1 — strip dev config from prod.

**Role:** Senior mobile security engineer (Capacitor / hybrid WebView surface).

**Task:** Inventory `capacitor.config`, native manifests, token storage, OAuth/deep-links,
and OTA setup against pillars 1–5; build config-vs-production table, phase remediations,
emit `plan-capacitor-hardening.md`. **Audit & plan only — no config, manifest, or code
edits until approved.**

**Audit the native layer the web view hides. Change nothing until approved.**

Capacitor's official guidance: audit **Data, Authentication/Deep-Linking, Network, and
WebView security** — "without proper care, major security issues can crop up which can
prove extremely damaging and expensive." Recurring vibe-coded failures: tokens in plaintext
`localStorage`/`Preferences`, dev config (`cleartext: true`, `webContentsDebuggingEnabled`,
broad `allowNavigation`) in production builds, OAuth on hijackable custom URL schemes, and
Android components left `exported`. **Every finding here is invisible to web-only review.**

---

## When this fires

Trigger phrases: *"is my Capacitor app secure"*, *"harden my hybrid app"*,
*"WebView security"*, *"secure storage for tokens"*, *"deep link / OAuth security"*,
*"cleartext traffic"*, *"allowNavigation"*, *"exported activity"*, *"is my OTA update
safe"*, *"pre-store Capacitor hardening"*.

Do **not** fire for: UI polish (`enhance-capacitor-ui`), RN perf (`mobile-rn-performance`),
or store paperwork (`plan-mobile-readiness` — pairs with this). This owns the *native-layer
security surface*.

---

## Why a dedicated skill

`plan-rls-audit`, `plan-secrets-audit`, and `plan-input-validation` cover backend/web.
None know `capacitor.config`, `AndroidManifest.xml`, `Info.plist`, the WebView bridge, or
Keychain/Keystore. Hybrid-native gaps are invisible if you only review web code.

---

## The audit — four pillars (+ OTA)  [HIGH freedom]

### 1 · Data security
- **Secrets in the bundle** — API keys, tokens hardcoded in JS or build-time env injection.
  Bundle is extractable; move secret-key ops server-side. → `plan-secrets-audit`
- **Plaintext token storage** — auth tokens in `localStorage`, `@capacitor/preferences`,
  IndexedDB, plain SQLite. Wrong. Use memory-only or **iOS Keychain / Android Keystore**
  (`@capacitor-community/secure-storage`, `capacitor-secure-storage-plugin`, Identity Vault).
- **`allowBackup`** (Android) — `android:allowBackup="true"` exfiltrates app data via backups.

### 2 · Authentication & deep linking
- **Custom URL scheme OAuth** — `myapp://` not globally owned; malicious app can intercept.
- **No PKCE** — OAuth2 in native apps **must** use PKCE.
- **Custom scheme vs App/Universal Links** — prefer Universal Links (iOS) / App Links
  (Android) with `assetlinks.json` / AASA. Note silent verification fallback to browser.
- **Exported Android components** — `exported=true` + `BROWSABLE` + no validation = common
  deep-link compromise. Audit every `<intent-filter>`.
- **Deep-link input not validated** — URI params → `WebView.loadUrl`, file access, queries.
  `startsWith` host checks inadequate.

### 3 · Network security
- **Non-HTTPS** — any `http://` in plaintext.
- **`cleartext: true`** in `capacitor.config` — dev/live-reload only; never production.
- **Missing `network_security_config`** (Android) — `usesCleartextTraffic="false"`; pinning
  for high-assurance (note bypass risks).

### 4 · WebView security
- **No / weak CSP** — `Content-Security-Policy` meta tag scoping loads.
- **Broad `allowNavigation`** — untrusted hosts load with native bridge attached; scope to
  trusted API origin(s). Note `getPlatform()` quirk on navigated URLs.
- **`webContentsDebuggingEnabled: true`** in production — remote inspection; gate to dev.
- **`addJavascriptInterface` / bridge exposure** — untrusted content + JS interface =
  Critical RCE vector.
- **`setAllowFileAccess*` / `file://`**, `eval` on user input, insecure `postMessage`.

### 5 · OTA / live-update governance
- **Update channel integrity** — OTA bundles signed/encrypted; unauthenticated path = RCE.
- **Store-policy** — Google permits WebView updates; **Apple: OTA must not alter core
  functionality** (App Store violation independent of security).
- **Rollback / kill switch** — can a bad update be reverted?

Severity aligned with Cap-go CAP001–010 / AND001–008 where applicable.

For each finding: location, gap, exposure, severity, remediation *direction*.

---

## Procedure  [HIGH freedom]

1. **Inventory.** Read `capacitor.config.*`, `AndroidManifest.xml`, `Info.plist`, network
   configs, OAuth/deep-link setup, token storage. State what you couldn't see.
2. **Run pillars 1–5.** Tag **Critical** (bridge/`addJavascriptInterface` to untrusted
   content, hardcoded signing key, plaintext secrets shipped), **High** (cleartext, broad
   `allowNavigation`, exported component, custom-scheme OAuth w/o PKCE, plaintext tokens),
   **Med** (debug flags, allowBackup, weak CSP), **Low** (logging nits).
3. **Config-vs-production table** — dev-only settings live in prod build.
4. **Phase** burndown. Production-config leaks and token storage first.
5. **Emit `plan-capacitor-hardening.md`. End the turn.**

---

## Guardrails  [LOW freedom — run exactly]

- **Plan only.** No config, manifest, or storage migration edits.
- **"Works in the browser" hides all of this.** Say so explicitly.
- **Dev config is a production vulnerability.** `cleartext`, `webContentsDebuggingEnabled`,
  broad `allowNavigation` must not ship.
- **In-memory or hardware-backed only** for tokens/keys — not `Preferences`/`localStorage`.
- **Universal/App Links + PKCE** for sensitive OAuth; custom scheme = High even if "works".
- **Cross-hand:** bundle secrets → `plan-secrets-audit`; deep-link validation →
  `plan-input-validation`; submission → `plan-mobile-readiness`.
- **Minimal quoting** of config/manifest.

## Self-critique before the burndown  [LOW freedom — do not skip]

1. **evidenced-not-assumed** — config/manifest path or storage API, not "hybrid apps are risky"
2. **plan-only** — no config, manifest, or storage migration this pass
3. **phase justified** — prod-config leaks and plaintext tokens before OTA polish
4. **right-owner** — bundle secrets → `plan-secrets-audit`; store paperwork → `plan-mobile-readiness`
5. **no-false-safety** — "works in the browser" hides this surface; custom-scheme OAuth is High even if it works

---

## Report template — `plan-capacitor-hardening.md`

```markdown
# Capacitor Native-Layer Hardening Audit — <app>

_Audit-only. Native security. Nothing changes until each phase is approved._
_These findings are invisible to web-only review._

## Scope
- Inspected: capacitor.config ☐ AndroidManifest ☐ Info.plist ☐ token storage ☐ OAuth ☐ OTA ☐
- Platforms: iOS ☐ Android ☐  | Assumptions: …

## Dev-config-in-production
| Setting | Prod value | Risk | Direction |
|---------|------------|------|-----------|
| server.cleartext | true | plaintext HTTP | false |
| webContentsDebuggingEnabled | true | remote inspection | dev-only |
| server.allowNavigation | ["*"] | bridge on untrusted origin | scope to API |

## Verdict
| Pillar | Crit | High | Med | Worst item |
|--------|------|------|-----|-----------|
| Data | n | n | n | tokens in localStorage |
| Auth/Deep-link | n | n | n | custom-scheme OAuth, no PKCE |
| Network | n | n | n | cleartext enabled |
| WebView | n | n | n | bridge exposed |
| OTA | n | n | n | unsigned channel |

## Findings
| # | Pillar | Location | Gap | Sev | Direction |
|---|--------|----------|-----|-----|-----------|

## Phased burndown
- **Phase 1 — Strip dev config from prod** → `mobile-capacitor-platform`
- **Phase 2 — Secure token storage** → `mobile-capacitor-platform` / `plan-secrets-audit`
- **Phase 3 — Auth & deep-link** → PKCE, App/Universal Links, exported review
- **Phase 4 — WebView & network** → CSP, network_security_config, bridge exposure
- **Phase 5 — OTA governance** → sign channel, store-policy, rollback

## Execution handoff
Re-audit native files after each phase; verify secure storage + deep-links on real devices.
```

---

## Chains with

- **Launch gates** — run with `plan-mobile-readiness` before store submit.
- **`plan-secrets-audit`** — bundle secrets cross-hand.
- **`plan-input-validation`** — deep-link / WebView input validation cross-hand.
- **Execution:** `mobile-capacitor-platform`, `backend-patterns`, `mobile-emulator-test`.
- **Verify:** real-device secure storage + deep-link test; no dev config in release build.

> Plan with a strong model; execute with `composer-2.5-execution.mdc`.
