@capgo/capgo-skills
Version:
55 agent skills for Capacitor mobile development
128 lines (93 loc) • 5.19 kB
Markdown
# Network Security
## HTTPS only
Release builds should make no `http://` or `ws://` requests. Defaults already help:
- Android blocks cleartext since API 28 (`net::ERR_CLEARTEXT_NOT_PERMITTED`).
- iOS App Transport Security (ATS) blocks HTTP (NSURLError -1022).
The usual regressions are config changes made for live reload that leaked into production.
### capacitor.config: keep dev settings out of release
`server.url`, `server.cleartext`, and `server.allowNavigation` are documented as "not intended for use in production." Gate them on an environment variable so release builds never contain them:
```typescript
import type { CapacitorConfig } from '@capacitor/cli';
const liveReloadUrl = process.env.CAP_SERVER_URL; // set only in your dev shell
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
...(liveReloadUrl && {
server: { url: liveReloadUrl, cleartext: liveReloadUrl.startsWith('http:') },
}),
};
export default config;
```
`capacitor.config.ts` is evaluated at `npx cap sync` / `copy` time, and the result is written to native assets. Always sync from a clean environment before a release build. On Capacitor 9, `npx cap run <platform> --url <dev-server-url>` handles live reload without editing the config.
### Android
`android/app/src/main/AndroidManifest.xml`:
```xml
<application
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config"
...>
```
`android/app/src/main/res/xml/network_security_config.xml`:
```xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- Dev only: put user CAs / cleartext for a local host in src/debug/res/xml -->
</network-security-config>
```
Put debug-only exceptions (local IP cleartext, user-installed proxy CAs for Charles/mitmproxy) in `android/app/src/debug/res/xml/network_security_config.xml` so they never reach release.
Also flag `android.allowMixedContent: true` in `capacitor.config.*` (documented as "not intended for use in production").
### iOS
Remove `NSAllowsArbitraryLoads` from `ios/App/App/Info.plist`. If a legacy HTTP host cannot be upgraded, scope an exception:
```xml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>legacy.example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
```
`NSAllowsArbitraryLoads` requires justification in App Review. Any exception is a finding to document.
## CORS vs CapacitorHttp
The WebView origin is `capacitor://localhost` on iOS and `https://localhost` on Android (default `server.androidScheme` is `https` since Capacitor 6). `fetch`/XHR from the WebView are subject to CORS.
Options:
1. Configure the backend to allow those two origins explicitly (preferred when you own the API).
2. Enable `plugins.CapacitorHttp.enabled: true`, which patches `fetch` and `XMLHttpRequest` to native HTTP (no CORS). It is bundled with `@capacitor/core`.
Never respond `Access-Control-Allow-Origin: *` together with credentials, and do not add wildcard CORS to an authenticated API to "fix" the app.
## Certificate pinning
Pinning stops MITM via a user-installed or compromised CA. It also breaks the app if the pinned certificate rotates without a backup pin, so confirm the rollout plan with the user first.
`@capgo/capacitor-ssl-pinning` (Capacitor 8) pins requests made through CapacitorHttp on iOS and Android:
```bash
npm install @capgo/capacitor-ssl-pinning
npx cap sync
```
```typescript
// capacitor.config.ts
plugins: {
CapacitorHttp: { enabled: true }, // required: pinning hooks CapacitorHttp
SSLPinning: {
certs: ['sslCerts/production/primary.cer', 'sslCerts/production/backup.der'],
excludedDomains: ['https://analytics.example.com'],
},
},
```
- Paths are relative to the app root. `cap sync` copies them into `webDir/certs` and converts PEM `.cer` to DER for iOS.
- Ship at least two pins (current plus backup/intermediate). Rotate the backup in before the primary expires.
- Requests that bypass CapacitorHttp (third-party native SDKs, WebSockets, images loaded by the WebView) are not covered. On iOS the plugin also answers WebView auth challenges; verify behavior per platform.
- `SSLPinning.getConfiguration()` returns the active native configuration. Use it in a debug screen to confirm certs loaded.
- Android-only alternative without a plugin: `<pin-set>` with an `expiration` date in `network_security_config.xml`. It applies to the WebView too, so test thoroughly.
Docs: https://capgo.app/docs/plugins/ssl-pinning/
## Request hygiene
- No tokens, emails, or passwords in query strings (capsec NET008): URLs end up in server logs and analytics.
- Use `wss://` for WebSockets (capsec NET004).
- Send auth in the `Authorization` header. If using cookies on native, `plugins.CapacitorCookies.enabled` patches `document.cookie`; mark cookies `Secure` and `HttpOnly` server-side.