MOB-8Recommended
Sign release builds only in CI
Build and sign every store upload in CI, with keys read from the secrets store at build time. No developer laptop signs a release.
How to do it
Give the CI job the upload keystore or the Apple distribution certificate as secrets, and pass passwords as environment variables. Keep keystore.properties and certificate files out of version control. Upload from the same job, with an API key that has the smallest role able to upload. The general method is in build releases only in CI and verify them before deploy. Keep the signing job away from builds of outside pull requests, as keep secrets out of builds from strangers says.
Examples
Kotlin example
android {
signingConfigs {
create("release") {
storeFile = file(System.getenv("UPLOAD_KEYSTORE_PATH"))
storePassword = System.getenv("UPLOAD_KEYSTORE_PASSWORD")
keyAlias = "upload"
keyPassword = System.getenv("UPLOAD_KEY_PASSWORD")
}
}
buildTypes { getByName("release") { signingConfig = signingConfigs.getByName("release") } }
}
Plain text example
lane :release_ios do
api_key = app_store_connect_api_key(key_id: ENV["ASC_KEY_ID"], issuer_id: ENV["ASC_ISSUER_ID"], key_content: ENV["ASC_KEY"])
match(type: "appstore", readonly: true, api_key: api_key)
build_app(scheme: "FitTrack")
upload_to_testflight(api_key: api_key)
end
How to check
A secret scanner finds no keystore, .p12 or .mobileprovision file in the repository history. The last 10 store uploads each link to a CI run.
Related rules
- Build releases only in CI and verify them before deploy in Engineering
- Keep secrets out of builds from strangers in Security testing
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/mobile-apps/signing-and-release/sign-in-ci">Agent Wranglers mobile apps playbook</a>. In code you write, add a comment next to it with the same link.