Developer workflows

Automate mobile app security in CI/CD

How to automate mobile app security in CI/CD for Android and iOS: least-privilege identities, the order of protect, sign and test, and failure handling.

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.

JobNeedsMust not have
Build and protectSource read access, dependency cache, protection profileSigning keys, store credentials, source write access
SignSigning key or signing service accessStore publishing rights
TestSigned protected build, test devicesSigning keys
PublishStore upload credential, approved artefactSigning 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.

  1. Build

    Compile the release build from a tagged commit with a pinned toolchain.

  2. Protect

    Apply obfuscation, anti-tampering and runtime protection with the approved profile, before signing.

  3. Sign

    Sign with the upload key or distribution certificate, in a job that holds only that secret.

  4. Test

    Run functional and security tests on the signed protected build, on real devices.

  5. Record and publish

    Store the artefact with its commit, toolchain and profile version, then upload.

A release sequence. Each step hands the next an artefact and a record; the publish step accepts only an artefact whose record is complete.

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
Automation removes the steps where keys leak and releases go out unprotected.

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.

FailureExample causePipeline response
Protection step fails or completes partiallyUnsupported build setting, profile errorStop the release
Signature verification failsWrong key or certificate loadedStop the release
Protected build fails functional testsProtection interacts with reflection or dynamic codeStop the release
Network timeout fetching a dependencyTransient registry or mirror issueRetry, then stop
Test device farm unavailableCapacity or outageRetry, then stop
Store upload rejectedMissing privacy declaration, version code reusedStop and alert the release owner
Runtime signal rate rises after releaseNew device model, false positivesAlert and review the response policy
Example failure policy: responses by type
Stop the release4Retry, then stop2Alert and review1

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.

Common questions

  • Does the Otukan protection step need our signing keys?

    No. Otukan's protection step never needs signing keys or store credentials. They stay in your CI secret store or signing service and are used only by the signing job. Otukan runs as a step in the build and release pipeline you already use, on every release build.

  • Should protection run before or after signing?

    Before. Signing seals the artefact, so anything that modifies the app, including obfuscation, anti-tampering and runtime protection, must run first. Testing comes after signing, on the signed protected build and on real devices, because a suite that passed on the unprotected build proves little about the one users install.

  • What happens if the protection step fails in the pipeline?

    The release stops. A failed or partial Otukan protection returns a failing exit code, so the job fails and the build does not move on to signing. Retries are for idempotent steps such as fetching dependencies or reaching a device farm, never for a step whose failure could mean the artefact was changed.

  • How do we roll back a protected release?

    Ship the previous commit with the previous protection profile through the same pipeline, with a new version code or build number, because the stores do not accept a reused or lower one. Keep the profile under version control, review its changes like code, and record its version with every artefact.

  • Can we trace which profile protected a given release?

    Yes. 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. Keep mapping files, native debug symbols and dSYMs next to each artefact, because crash reports are only readable with the files from that exact build.

Keep reading

Protect every release build

Tell us your CI/CD setup, platforms, frameworks and how you sign and release today. We never ask for binaries, signing keys, CI secrets or production credentials.

Or request a technical demo