Automating mobile app security in CI/CD means that every release build of your Android and iOS apps is protected, signed, tested and recorded by the pipeline itself, with no manual stage in between. To evaluate an approach, look at three things: which identities and secrets each job can reach, the order in which protection, signing and testing run, and what happens when a step fails. This article covers each one for Android and iOS pipelines, with examples of generic platform configuration.
Authority: identities, permissions and secrets
A pipeline is a set of jobs acting on your behalf. Each job should have its own identity and only the permissions its step needs. The most common mistake is one pipeline identity that can read the source, use the signing key and publish to the store.
| Job | Needs | Must not have |
|---|---|---|
| Build and protect | Source read access, dependency cache, protection profile | Signing keys, store credentials, source write access |
| Sign | Signing key or signing service access | Store publishing rights |
| Test | Signed protected build, test devices | Signing keys |
| Publish | Store upload credential, approved artefact | Signing keys, source write access |
Two platform facts shape this table. On Android, with Play App Signing, Google holds the app signing key and your pipeline signs with an upload key, so the key your CI uses is not the key your users’ devices check. On iOS, signing happens on a macOS runner with Xcode, using a distribution certificate and provisioning profile; keep those in your secret store and load them only into the signing job.
Android signing configuration should read secrets from the environment the signing job provides, never from files in the repository:
android {
signingConfigs {
create("release") {
storeFile = file(System.getenv("UPLOAD_KEYSTORE_PATH"))
storePassword = System.getenv("UPLOAD_KEYSTORE_PASSWORD")
keyAlias = System.getenv("UPLOAD_KEY_ALIAS")
keyPassword = System.getenv("UPLOAD_KEY_PASSWORD")
}
}
}
Sequence: build and protect, sign, test, record
Order matters because signing seals the artefact. Anything that modifies the app must happen before signing; anything that trusts the app must happen after.
Build
Compile the release build from a tagged commit with a pinned toolchain.
Protect
Apply obfuscation, anti-tampering and runtime protection with the approved profile, before signing.
Sign
Sign with the upload key or distribution certificate, in a job that holds only that secret.
Test
Run functional and security tests on the signed protected build, on real devices.
Record and publish
Store the artefact with its commit, toolchain and profile version, then upload.
Testing the protected build, not the unprotected one, is the step teams most often skip. Protection changes the binary; a test suite that passed on the unprotected build proves little about the one users install. For test design, see protected mobile app testing.
After signing, verify the signature in the pipeline before publishing. These are standard platform tools:
# Android App Bundle: confirm the upload key signature
jarsigner -verify -certs app-release.aab
# Android APK used for device tests: print the signing certificate
apksigner verify --verbose --print-certs app-release.apk
# iOS: confirm the code signature of the app inside the unzipped IPA
codesign --verify --deep --strict --verbose=2 Payload/MyApp.app
# Both platforms: record a digest of each artefact you are about to publish
shasum -a 256 app-release.aab MyApp.ipa > release-digests.sha256
Otukan runs as a step in your CI/CD pipeline on every release build. Every protected build records the commit, toolchain and protection profile version behind it, with logs you keep, and the same inputs and profile produce an equivalent protected artefact. Signing detail is covered on mobile app signing security.
Protection as a manual stage
- Someone downloads the build, protects it and uploads it again
- Signing keys end up on a laptop
- Which profile protected which release is in someone's memory
- Hotfix releases skip the step under time pressure
Protection as a pipeline step
- Every release build is protected the same way
- Signing keys stay in the secret store, used only by the signing job
- The record links commit, profile and artefact
- Hotfixes follow the same path, because there is only one path
Operations: failures, retries, versioning and rollback
A pipeline is judged by what it does on a bad day. Write the failure policy down before the first incident, and make the pipeline enforce it.
| Failure | Example cause | Pipeline response |
|---|---|---|
| Protection step fails or completes partially | Unsupported build setting, profile error | Stop the release |
| Signature verification fails | Wrong key or certificate loaded | Stop the release |
| Protected build fails functional tests | Protection interacts with reflection or dynamic code | Stop the release |
| Network timeout fetching a dependency | Transient registry or mirror issue | Retry, then stop |
| Test device farm unavailable | Capacity or outage | Retry, then stop |
| Store upload rejected | Missing privacy declaration, version code reused | Stop and alert the release owner |
| Runtime signal rate rises after release | New device model, false positives | Alert and review the response policy |
Counts come from the failure-handling table above, as an example of how to read your own policy. A pipeline where most failures stop the release is the safer default.
A failed or partial Otukan protection returns a failing exit code, so the job fails and an unprotected build never reaches signing. Retries are safe only for steps that are idempotent: fetching dependencies, reaching a device farm. Never retry a step whose failure could mean the artefact was changed.
Versioning and rollback
Treat the protection profile as code. Keep it under version control, review changes the same way as code changes, and record its version with every artefact. Rolling back an app release then means shipping the previous commit with the previous profile, through the same pipeline, with a new version code or build number, because the stores do not accept a reused or lower one.
Pipeline readiness checklist
- Each job has its own identity, and only the signing job can use signing keys.
- No signing key, keystore password or store credential is stored in the repository.
- Protection runs on every release build, including hotfixes.
- A failed or partial protection step stops the release.
- Tests run on the signed protected build, on real Android and iOS devices.
- Every artefact is stored with its commit, toolchain, profile version and digest.
- Retries apply only to idempotent steps.
- Rollback ships the previous commit and profile through the same pipeline.