Secrets management
Skill almasumdev/awesome-mobile-security-agent-skills/.github/skills/secrets/secrets-management
Agent skills for securing mobile apps: storage, transport, auth, obfuscation, and hardening.
npx -y skills add almasumdev/awesome-mobile-security-agent-skills --skill secrets-managementAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Handling secrets for mobile clients — why the client should not hold secrets, how to use a config server, and how App Check / Play Integrity replace API keys. Use when adding third-party SDKs or new server endpoints.
SKILL.md
4.7 KB, as published. Nobody here has run it
Secrets Management for Mobile Clients
Instructions
The first rule of mobile secrets: the client cannot keep a secret. Anything embedded in your APK / IPA is extractable in minutes. Design accordingly.
1. What's Actually a Secret
| Value | Can ship in the app? |
|---|---|
OAuth client_id (public client) | Yes |
OAuth client_secret | No — do not use confidential clients on mobile |
| Public keys (pinning, verification) | Yes |
| Private signing keys | No |
| Firebase / Maps / Analytics API key | Yes, but restrict by bundle ID + SHA |
| Third-party SDK secret (Stripe secret key, Twilio auth token) | No, never |
| Symmetric encryption key for local data | Generate on device, wrap with Keystore |
If the vendor says "use this secret from the mobile SDK", they mean the publishable key. Their secret key belongs on your server.
2. Restrict, Don't Hide
"Public" API keys (Firebase, Google Maps) can still be abused. Restrict at the provider:
- Google Cloud keys → restrict to
packageName + SHA-256 signing certificate(Android) orbundleId + teamId(iOS). - Stripe → use
pk_live_*publishable keys only on the client; neversk_live_*. - Sentry DSN → scoped to a single project with rate limits; safe to ship.
Abuse detection and quota limits still matter even on "public" keys.
3. Use a Config Server
For anything dynamic, fetch from your backend at launch:
// Pseudocode, same pattern on iOS / Android / Flutter / RN.
async function bootstrap() {
const config = await api.get("/config", {
headers: { "X-App-Attestation": await attest() },
});
featureFlags.apply(config.flags);
runtimeSecrets.apply(config.scopedCredentials); // short-lived, per-user
}
The server:
- Requires a valid user session or device attestation.
- Returns scoped, short-lived credentials (signed URLs, STS tokens) — not long-lived API keys.
- Logs every issuance for audit.
4. Short-Lived Scoped Credentials
Instead of embedding AWS keys, upload via a signed URL:
- Client asks server: "I want to upload an avatar."
- Server: issues an S3 pre-signed PUT URL valid for 5 minutes, scoped to one object.
- Client: PUTs bytes directly. Server never proxies the upload.
Same pattern for database tokens (AWS STS / GCP Workload Identity Federation), Twilio short-lived tokens, etc.
5. App Check / Play Integrity as the Gatekeeper
Combine attestation with secrets issuance:
- Server only issues the signed URL / STS token if the request carries a valid App Attest assertion or Play Integrity token.
- Expired or missing attestation → 403, regardless of user auth.
This turns "anyone with the APK can hit our /upload endpoint" into "only a verified install of our app can".
6. Build-Time Injection Is Not Hiding
Patterns like:
val API_KEY = BuildConfig.API_KEY // read from gradle.properties
are convenient but not secure. The value ends up in plaintext in strings.xml, BuildConfig, or the binary. Use this only for values that are "public with restrictions" (category 1 above), and document that the value is not a secret.
7. WebViews and JS Bridges
- Never inject a secret into a WebView via
loadUrl("javascript:...")— it lives in the page's memory and can be exfiltrated. - Never expose a bridge method that returns a token without authentication.
- For OAuth, always round-trip through the system browser (oauth-mobile).
8. Rotating a Leaked Secret
When (not if) a secret leaks:
- Rotate at the provider immediately.
- Update the server to accept both old and new briefly.
- Release a client update that picks the new value from config (not a hardcoded binary update).
- Revoke the old value at the provider.
- Audit what the leaked secret could access; treat it as a breach of that scope until proven otherwise.
Hardcoded secrets require a full client release to rotate — another argument against them.
Checklist
- No OAuth
client_secret, Stripe secret key, or vendor admin token is in the binary. - Publishable / public keys are restricted by bundle ID + signing SHA at the provider.
- Scoped, short-lived credentials are used for cloud resources (S3, STS, Twilio).
- App Attest / Play Integrity gates secret / token issuance endpoints.
- No secret is exposed to a WebView or JS bridge.
- Secret rotation does not require a client-side binary update.
- A documented incident playbook covers secret leak → rotate → audit.