Apple describes a code-signing identity as two linked components: a certificate and its corresponding private key (Apple’s code-signing certificate documentation). That distinction leads to the central decision: do not leave signing private keys long term on a reusable shared runner. Keep credential custody separate from runtime use, allow only trusted release workflows to access the minimum required credentials, and import them into a controlled runtime keychain for the signing job.
Symptom → A build can sign successfully, but nobody can show who could access its imported key or what remained after the job.
Fastest fix → Restrict release credentials to trusted workflows, isolate the production signing runner from less-trusted jobs, and require evidence for import, use, cleanup, and log handling.
This guide is for enterprise IT and security leads reviewing iOS release credentials; platform engineers designing GitHub Actions macOS runner workflows; and iOS leads deciding whether a shared build node can handle production signing.
GitHub Actions macOS runner iOS signing key management begins with boundaries
The storage location is only one part of the control design. A credential store governs how a secret is held and made available; a macOS keychain is a place where signing tools can use imported identity material during execution. Neither, on its own, establishes that different jobs on a reusable Mac are isolated from one another.
Apple’s documentation explains the relationship between certificates and private keys, while its guidance for sharing team signing certificates addresses how signing identities are shared within a team. Those sources define what the signing asset is; they do not certify a particular CI layout as safe. Review Apple’s guidance on sharing team signing certificates before choosing who should hold and distribute the identity.
| Control area | Long-term responsibility | Runtime responsibility | Evidence to request |
|---|---|---|---|
| Credential custody | Keep the approved signing material in a controlled source, such as GitHub Actions Secrets or an external credential-management system | Do not treat the runner’s local disk as the approved long-term store | Named owners, access policy, and a documented rotation and revocation process |
| Workflow authorization | Define which repositories, branches, and release environments may request credentials | Pass only the values needed by the approved signing job | Workflow configuration and environment approval records |
| Signing execution | Not a place to retain credentials between jobs | Import the identity into a job-scoped keychain or another controlled runtime context | Import and signing logs that do not disclose secret values |
| Runner isolation | Separate access policy from physical or virtual job execution | Prevent unrelated or lower-trust jobs from sharing a signing context | Runner assignment, account boundaries, workspace controls, and post-job checks |
| Closure and review | Retain suitable audit evidence without retaining secret material | Remove imported material and clean the job’s working area | Cleanup results, log review, and a record of who reviewed the run |
This division is a governance model, not a claim that GitHub or Apple mandates this exact table. The team must map each responsibility to controls it can actually configure and verify.
Credential custody and workflow access
GitHub Actions Secrets and an external credential-management system address credential availability and administration in different ways. GitHub documents how Actions secrets are configured and consumed; an external system may introduce its own access rules, audit features, and operational responsibilities. Those capabilities depend on the specific system and its configuration, so a design review should verify them against the relevant documentation rather than assume that every external store offers the same controls. Check GitHub’s Actions Secrets reference for the platform’s documented behavior.
A useful selection test is to trace the complete path from owner to signing process:
- Custody: Who approves creation, access, rotation, and revocation of the signing material?
- Workflow scope: Which repository and workflow can request it? Can a pull request or an unexpected branch reach the same credential path?
- Runtime scope: Where does the material exist after the workflow receives it, and which local users or processes can access that location?
- Review: What record shows that access and changes were reviewed, without copying the secret itself into logs or tickets?
GitHub’s security guidance warns that workflows and actions can create security risks, especially when untrusted code can influence execution. Use GitHub’s secure-use guidance to assess the workflow’s trust assumptions. Treat access to a release secret as an authorization decision, not as a routine build setting.
For teams using GitHub Environments, approvals and protection rules can add a release gate, depending on the configuration. They do not turn a shared runner into an isolated execution environment. Review GitHub’s documentation for deployments and environments, then inspect the runner separately.
Runtime isolation on a shared Mac
The key question is not just whether a keychain is temporary. It is whether the entire signing job has a defensible boundary from other jobs and users on that Mac.
A macOS keychain is an execution-time mechanism for making identity material available to signing tools. Apple exposes an API for creating a keychain, but using that API does not itself prove that a job is isolated, that cleanup covers every relevant copy, or that a later job cannot access residual state. See Apple’s keychain creation API documentation.
For a reusable self-hosted runner, examine at least four linked surfaces:
- Runner and account: Identify which jobs can reach the same runner and whether they execute under a shared local account.
- Keychain and import: Determine how the signing identity is imported, which process can unlock it, and how the signing command accesses it.
- Workspace and temporary files: Check where decoded credentials, profiles, intermediate files, and build artifacts may be written.
- Job completion: Inspect what removes the keychain and working files, and test whether another job can observe leftover state.
GitHub’s self-hosted runner documentation describes the operational characteristics and responsibilities of self-hosting. Its security guidance also makes clear why accepting untrusted workflows on a runner matters. Read GitHub’s self-hosted runner documentation alongside the secure-use guidance. A job-specific keychain can reduce the time or scope of credential exposure, but it should not be described as a complete isolation guarantee.
Permission controls for release workflows
Limit signing access to workflows whose source and execution path the organization trusts. Review the trigger, branch protections, environment rules, reusable workflows, and third-party actions used before a job receives credentials. A workflow that can be altered by a less-trusted contributor should not automatically inherit production signing access.
GitHub supports workflow-level permissions for the GITHUB_TOKEN; configure that token for the minimum access the workflow needs. This is useful for reducing token authority, but it is distinct from the handling of an Apple signing private key. Consult GitHub’s workflow syntax documentation when reviewing token permissions.
For third-party actions, the review should cover the action’s source, versioning and update process, and the data it can access during the job. Avoid passing signing material to steps that do not need it. Keep the release job narrowly scoped: build and sign the intended artifact, then publish through the approved path.
Approvals are a control over whether a job proceeds. They are not proof that the machine executing it is separated from other jobs. If a team cannot show which repositories can use a runner, how local accounts and workspaces are separated, and what happens after each job, the appropriate response is to reduce runner sharing or move production signing to a separately controlled node.
Cleanup evidence and operational review
A cleanup script is necessary operational hygiene, but its successful exit status does not establish that all credential copies or processes are gone. Build an acceptance record around observable evidence, and distinguish platform-supported mechanisms from additional controls the team has implemented and tested.
A practical review can follow this sequence:
- Map the credential path. Record the approved custody location, the workflow that requests access, and each place where the credential may exist during execution. Keep values and sensitive log excerpts out of the record.
- Review workflow eligibility. Confirm the release trigger, branch restrictions, environment authorization, and third-party action dependencies. Record which workflows must never receive production signing credentials.
- Inspect runner assignment. Identify which teams and repositories can reach the signing node. Check the local account, workspace, and job reuse model rather than inferring isolation from repository settings.
- Test controlled import. Run the approved signing workflow with non-production test material where possible. Verify that the intended keychain is created and that the signing step uses the expected identity.
- Observe job completion. Check that the workflow attempts to remove the temporary keychain and sensitive files, then test the node for residual artifacts using an approved validation procedure.
- Review logs and artifacts. Confirm that the workflow does not print secrets or upload credential-bearing files as artifacts. Check the logs and artifact configuration, not only the shell script.
- Record exceptions and owners. Capture failed checks, remediation responsibility, and the decision on whether the node remains eligible for production signing.
The team should define what counts as acceptable evidence before the pilot begins. For example, a reviewer can verify the workflow configuration, inspect a sanitized job record, and run a controlled post-job check. These are team-defined acceptance controls; they should not be represented as guarantees made by GitHub or Apple. Repeat the tests after changes to runner assignment, job cleanup, keychain handling, or release workflow permissions.
Selection score and decision branches
Use a qualitative rating for each control area: Strong means the team can demonstrate the boundary with current evidence; Conditional means there is a gap with a named owner and a limited trial plan; Fail means the boundary is unknown or cannot be tested. This is an internal review tool, not a vendor security score.
Apply the following decision branches:
- If one trusted release team owns the workflow, access is narrowly granted, the runner is separated from lower-trust jobs, and cleanup checks are recorded, then evaluate controlled credential injection with a job-scoped keychain for production use.
- If several teams or repositories share the Mac, but the trust levels differ, then separate runner access and signing credentials before enabling release signing. Do not rely on encrypted storage alone to compensate for shared execution.
- If an untrusted or broadly accessible workflow can run on the same node, then keep production signing credentials out of that node until the execution boundary changes.
- If the team cannot verify cleanup, log handling, or account and workspace boundaries, then do not assign production signing work to the shared runner. Use a dedicated, reviewable environment for the pilot instead.
- If the workload requires a persistent, heavily customized host or physical interfaces that a remote Mac environment cannot provide, then compare self-owned hardware and other infrastructure options rather than renting by default.
The outcome should be a recorded decision supported by a workflow permission inventory, runner-sharing map, pilot results, and cleanup evidence. A green status in one area does not cancel a failed result elsewhere: in particular, strong secret storage cannot compensate for an unverified execution boundary.
Frequently asked questions
Can a self-hosted macOS runner safely keep an iOS signing private key?
A self-hosted runner can perform signing, but keeping a private key on a reusable shared runner creates a separate persistence and cross-job risk that encrypted storage alone does not remove. Use it for production only when you can demonstrate trusted workflow access, adequate separation between jobs and teams, controlled key import, and verifiable cleanup. Otherwise, isolate the runner before granting release credentials.
Should signing credentials go in GitHub Secrets or the macOS keychain?
They serve different purposes. GitHub Actions Secrets can provide workflow-scoped access to values, subject to repository, organization, or environment configuration. The macOS keychain is a runtime location used by signing tools after credentials are made available to the job. Neither choice alone proves that a reusable runner has isolated jobs or removed imported material afterward.
How can separate repositories be prevented from reading credentials on the same Mac?
Start by separating runner access according to trust, rather than assuming that different repositories create separate macOS environments. Restrict which workflows can use release credentials, avoid placing persistent signing material in shared accounts or directories, and test whether one job can inspect another job's files or keychain state. If that boundary cannot be demonstrated, use a separate runner for production signing.
What should happen to an imported signing key after a job finishes?
Design the job to remove the temporary keychain and imported signing material, clean its working directory, and keep sensitive values out of logs. Then verify the result through runner-side checks and recorded job evidence; a successful cleanup command is not proof that every copy or process state is gone. If reliable cleanup cannot be demonstrated, do not reuse that node for trusted production signing.
Before choosing an environment, inventory the signing workflows, runner-sharing scope, and cleanup evidence. A general-purpose shared Mac can leave persistent local state, mix jobs with different trust levels, and make cleanup hard to verify. If the existing setup cannot provide a defensible boundary, a separately managed remote Mac may be worth testing against the same acceptance criteria; review ArmMacs rental plans and Mac support information, then contact ArmMacs to clarify whether the available environment fits the pilot. Renting is not automatically the right answer for long-running, tightly customized workloads or tasks that require physical interfaces, so compare those constraints before committing.
Frequently asked questions
Can a self-hosted macOS runner safely keep an iOS signing private key?
A self-hosted runner can perform signing, but keeping a private key on a reusable shared runner creates a separate persistence and cross-job risk that encrypted storage alone does not remove. Use it for production only when you can demonstrate trusted workflow access, adequate separation between jobs and teams, controlled key import, and verifiable cleanup. Otherwise, isolate the runner before granting release credentials.
Should iOS signing credentials go in GitHub Secrets or the macOS keychain?
They serve different purposes. GitHub Actions Secrets can provide workflow-scoped access to values, subject to the repository, organization, or environment configuration. The macOS keychain is a runtime location used by signing tools after credentials are made available to the job. Neither choice alone proves that a reusable runner has isolated jobs or removed imported material afterward.
How can separate repositories be prevented from reading the same Mac's signing credentials?
Start by separating runner access according to trust, rather than assuming that different repositories create separate macOS environments. Restrict which workflows can use release credentials, avoid placing persistent signing material in shared accounts or directories, and test whether one job can inspect another job's files or keychain state. If that boundary cannot be demonstrated, use a separate runner for production signing.
What should a GitHub Actions job do with an imported signing key when it finishes?
Design the job to remove the temporary keychain and imported signing material, clean its working directory, and keep sensitive values out of logs. Then verify the result through runner-side checks and recorded job evidence; a successful cleanup command is not proof that every copy or process state is gone. If reliable cleanup cannot be demonstrated, do not reuse that node for trusted production signing.
Further reading
ArmMacs Cloud Mac
Validate Your Signing Workflow on a Dedicated Mac
Run your CI jobs on an ArmMacs dedicated physical Mac, not a virtual machine.
