Safeguard
Developer Tools•

How do you store an Apple signing certificate in CI without leaking it?

Keep the p12 and profile as encrypted CI secrets, use a temporary keychain on the runner, limit which jobs read them, and clean up self-hosted runners.

Safeguard Security Team
4 min read

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:

SecretContents
CertificateThe p12 file, converted to Base64
Certificate passwordThe password protecting the p12
Provisioning profileThe profile file, converted to Base64
Keychain passwordAny 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

  1. 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.
  2. 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.
  3. 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.
  4. Pin actions. A third-party action in the signing job runs with the secrets in scope.
  5. 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

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.