Store the certificate file, its password and the provisioning profile as encrypted CI secrets, import them into a temporary keychain at build time, and delete that keychain afterwards. Limit which jobs can read the secrets, and remember that Base64 is encoding, not encryption.
What GitHub's guide recommends
GitHub's documentation on signing Xcode apps in Actions describes a process of storing certificates and provisioning profiles, transferring them to the runner, importing them into the runner's keychain, and using them in your build. It strongly recommends using GitHub secrets for this. Four secrets are involved:
| Secret | Contents |
|---|---|
| Certificate | The p12 file, converted to Base64 |
| Certificate password | The password protecting the p12 |
| Provisioning profile | The profile file, converted to Base64 |
| Keychain password | Any new random string, since a new keychain is created on the runner |
The runner steps
The documented workflow decodes the secrets into files in the runner's temporary directory, creates a temporary keychain with the random password, imports the certificate, sets the key partition list so Apple tools can use the key, adds the keychain to the search list, and copies the profile into the standard profile folder. For iOS targets the profile extension is mobileprovision, and for macOS targets it is provisionprofile.
You do not need to memorize the commands. Copy them from the guide and adapt them.
Limits of Base64
GitHub's secrets guide says Base64 can store small binary blobs as secrets, and notes that Base64 only converts binary to text and is not a substitute for encryption. The protection comes from the secret store and its access rules. Secrets over 48 KB need a different approach, such as encrypting a file and storing only the passphrase as a secret. A p12 is normally small enough.
Protect the secrets
- Scope. Put signing secrets in an environment, not at the repository level, so only the release job can read them. GitHub lets you create secrets for an environment, and environments can have protection rules.
- Avoid the command line. GitHub's guidance is to avoid passing secrets between processes on the command line where possible, since other users may see command lines.
- Review who can change the workflow. Anyone who can edit the release workflow can try to print the secrets. Use code owners and branch protection on workflow files.
- Pin actions. A third-party action in the signing job runs with the secrets in scope.
- Rotate. If a runner or workflow was compromised, treat the certificate as exposed and replace it.
Clean up self-hosted runners
GitHub-hosted runners are virtual machines destroyed at the end of the job, so certificates used on them are destroyed too. GitHub warns that on self-hosted runners the temporary directory is cleaned, but the keychain and profile might still exist. It tells you to add a final step that deletes them, run even when earlier steps fail. Without it, the next job on that machine, possibly from another repository, could use your signing identity.
Add a gate before the signing job
A signing identity should be reachable only from a build that passed your checks. Try the demo below to see how a pull request gate decides what may continue.
Try it yourself
Click through it at your own pace, or open it full screen.
Look for the point where a failed check blocks the next step, then picture your signing job sitting behind that point.
Where Safeguard fits
Safeguard offers policies and gates for pull requests, plus dependency scanning and SBOMs, so a build is checked before it reaches a signing step. A free scan is at gold.safeguard.sh/scan.
Quick check
Is a Base64 secret encrypted?
No. GitHub's documentation says Base64 only converts binary to text. The secret store provides the protection.
Do I need cleanup on GitHub-hosted runners?
No. GitHub says they are destroyed after the job. On self-hosted runners, delete the keychain and profile in a final step.
Sources
- GitHub Docs: Installing an Apple certificate on macOS runners
- GitHub Docs: Using secrets in GitHub Actions
Last reviewed: 19 October 2026.