A shared Mac Runner still contains source code, signing material, user credentials, and network access after a job finishes.

Fastest solution: do not run public or untrusted code on a self-hosted Mac Runner; only admit a private repository after restricting triggers, isolating its Runner Group, separating signing work, and proving that the node can be cleaned or rebuilt.

This week, classify every repository, test allowed and rejected routing, move signing to a separate node, and record what remains after a deliberately dirty job. If the Mac cannot be rebuilt or verified clean, keep it on low-privilege builds only.

01

Who should use this runbook?

This guide is for mobile teams running Xcode builds and Simulator tests in GitHub Actions.

It is also for DevOps and platform engineers who own Runner Groups, repository permissions, network boundaries, and Mac operations. Security and release owners will find the credential and approval checks useful when Apple certificates, macOS Keychain, and production publishing are involved.

A private repository is not automatically a trusted execution source. The security decision depends on who can change workflow files, who can trigger jobs, what the runner can reach, and whether the host remains trustworthy after a job.

02

Trust first: private does not mean safe

GitHub’s security guidance warns that self-hosted runners do not provide the same clean, temporary isolation expected from hosted execution. A workflow can leave files, processes, credentials, or system changes behind. Public repositories and untrusted contributions therefore create a persistent-host risk, not just a failed-build risk. See GitHub’s secure use reference for Actions.

Can a GitHub Actions self-hosted Runner serve a public repository?
Our default answer is no when the runner has access to signing identities, internal services, deployment credentials, or a persistent user account. A public repository can accept code changes from sources that the runner administrator does not control. Even when a workflow requires approval, the Mac may already have been exposed before a human reviews the result.

Use this admission matrix before assigning a label or registering a new runner:

Repository and trigger condition Runner decision Required evidence
Public repository or untrusted pull request Reject The workflow cannot route to the sensitive Mac
Controlled repository, but broad contributor or workflow-edit access Isolate for low-privilege testing Trigger and workflow-change permissions are documented
Private repository with restricted maintainers and reviewed workflows Consider production admission Runner Group, credentials, cleanup, and network tests pass
Any repository with unknown ownership or unclear workflow approval Reject Ownership and trigger authority must be resolved first

The relevant question is not whether the runner shows as online. It is whether the repository can submit code that the host owner would refuse to execute.

Operational warning: an online status proves connectivity, not isolation, cleanliness, or safe authorization. Treat it as a liveness signal only.

Record these owners before testing:

  • The people who can edit workflow files.
  • The people who can trigger workflows from branches, forks, or tags.
  • The people who approve protected environments.
  • The people who can register, remove, or relabel runners.
  • The people who can access the Mac account and its Keychain.

If any answer is unknown, the node is not ready for signing or publishing.

03

First boundary: route jobs by policy, not by label

Runner Groups provide an access boundary, but the boundary is only effective when repository access, labels, and workflow conditions agree. GitHub documents how Runner Groups control repository access. The workflow’s runs-on selection then determines which eligible runner can receive a job; review GitHub’s runner selection and routing guidance.

A label should describe capability, not act as the entire security policy. Labels can be misapplied, copied into another workflow, or left behind after a node changes role. Combine the following controls:

  • Register the Mac at the narrowest suitable scope.
  • Put signing-capable nodes in a dedicated Runner Group.
  • Allow only named repositories to use that group.
  • Use labels for workload selection, such as build or simulator capability.
  • Restrict workflow triggers and branch sources.
  • Review who can modify the workflow that selects the runner.
  • Test both a permitted job and a deliberately rejected job.

How can a shared Mac Runner stop one repository from reading another repository’s files?
Do not rely on workspace deletion alone. Start with separate Runner Groups and repository allowlists. Then use separate operating-system accounts or separate nodes where the threat model requires stronger boundaries. A job that can execute arbitrary code as the same account may read any files that account can read, including another checkout, user configuration, logs, or Keychain items.

The routing test should produce evidence, not screenshots. Save the workflow run identifier, repository name, selected group, selected label, and the rejection result from a repository that is not allowlisted. Remove sensitive values from the evidence before sharing it.

04

Second boundary: separate build credentials from signing authority

Xcode CI usually combines several kinds of access, but they should not share one long-lived environment:

  • The workflow token used to read source or report status.
  • Repository deploy keys or package credentials.
  • Apple signing certificates and private keys.
  • Notarization or publishing credentials.
  • Internal service tokens.
  • SSH keys and local configuration.

GitHub’s secure-use guidance explains why workflow permissions and secrets must be limited to the job’s actual need. Protected environments can add a human approval step, but approval does not undo host compromise. If untrusted code already controls the Mac account, it may inspect files or processes before a release step reaches the approval gate. Review the environment protection documentation alongside the workflow permissions.

Where should Xcode signing certificates live?
Keep them on a dedicated signing or release node, not on a general-purpose shared build Mac. A build node can compile and run tests with no private signing identity when the pipeline permits that split. If signing must occur during the build, use a separate account, a narrowly scoped Keychain, restricted workflow access, and an explicit release approval.

Apple’s code-signing certificate documentation describes the certificate and private-key relationship. The security decision here is about the execution boundary: the account running xcodebuild, the Keychain it can unlock, and the processes that can inspect that session.

Asset or permission General build node Signing or release node
Source checkout Required only for assigned repositories Required only for the release input
Basic workflow token Narrow read or status scope Narrow scope plus release-specific permission
Apple private key Preferably absent Present only in the controlled signing account
Production publishing access Absent Available only for approved release jobs
Internal network access Limited to build dependencies Limited to publishing endpoints
Human approval Useful for sensitive changes Required before production release

The same account should not casually own source checkout data, private signing keys, SSH credentials, and broad internal network access. If the team cannot separate those roles on a persistent Mac, downgrade the node to non-sensitive builds.

05

Third boundary: prove the host is clean after every job

A cleanup script that removes the checkout directory is not a cleanliness proof. Build systems produce state outside the repository. On a Mac, inspect the workspace, temporary directories, DerivedData, logs, shell history where applicable, user-level configuration, launch agents, caches, credential files, and running processes.

What must a Mac Runner clean after each task?
At minimum, remove the repository checkout, untracked files, generated archives, DerivedData, simulator artifacts, temporary credentials, job logs containing secrets, package-manager credentials, and task-specific configuration. Then inspect for processes and persistence mechanisms that survive the cleanup command. Reboot or rebuild according to the node’s risk level.

Use this acceptance sequence:

  1. Run a low-privilege job that creates a uniquely named file, background process, temporary credential marker, and configuration change.
  2. End the job without running the normal cleanup helper.
  3. Execute the cleanup procedure under the same operational conditions as production.
  4. Search for the marker in the workspace, temporary locations, user files, logs, and process list.
  5. Check launch agents, scheduled tasks, shell configuration, network connections, and Keychain access records relevant to the account.
  6. Reboot the Mac and repeat the checks.
  7. Compare the result with a known-clean baseline or rebuild the node.
  8. Record every remaining artifact and decide whether it is acceptable, removable, or a stop condition.

A long-running self-hosted runner is contaminated when the team cannot explain a new process, altered configuration, unexpected credential trace, or unexplained network connection. The absence of an obvious file is not enough.

How do we judge whether a long-running self-hosted Runner is contaminated?
Stop sensitive dispatch when the node has unexplained persistence, cannot produce a trusted baseline, cannot rotate exposed credentials, or cannot be rebuilt within the team’s recovery procedure. Do not send a signing job to “see whether it still works.” Remove the runner from its group, preserve the relevant operational evidence, rotate potentially exposed secrets, and perform a rebuild or manual security review.

GitHub also documents the risk of compromised self-hosted runners. A just-in-time registration or removal event can narrow runner lifetime, but it does not prove that the underlying Mac was erased or that a malicious process did not alter the host before removal.

06

Fourth boundary: reduce network and host authority

A runner’s impact is shaped by what it can reach. Inventory the destinations required by normal jobs:

  • Source and package repositories.
  • Apple services required by the approved workflow.
  • Artifact storage.
  • Internal APIs.
  • Deployment systems.
  • Monitoring and logging endpoints.
  • Administrative networks.
  • SSH destinations.
  • DNS, proxy, and software update services.

Then create two lists: allowed destinations and expected rejections. Do not describe “internet access” as one permission. A build may need package downloads but not access to production administration. A release job may need an Apple endpoint but not an internal database network.

Review host privileges with the same discipline. A job should not receive root access merely because installation is convenient. Avoid full-disk access for ordinary builds. Put unavoidable privileged operations in a separate job, separate account, or separate node with an approval boundary.

Experience from incident review: broad outbound access turns a compromised build into a network pivot. The useful control is not a vague firewall claim; it is a recorded test showing that required destinations work and sensitive destinations fail.

07

Fifth boundary: make recovery part of admission

A safe design must answer what happens after a failed cleanup, suspected secret exposure, unexpected reboot, or runner process crash. GitHub’s self-hosted runner documentation should be reviewed whenever registration, removal, lifecycle, or communication behavior changes.

Use the following decision branches:

  • If the repository is public, accepts untrusted contributions, or permits unknown workflow changes, then reject the sensitive Mac and use a clean, isolated execution option.
  • If the repository is controlled but the Mac cannot be rebuilt or inspected reliably, then allow only low-privilege builds and block signing and publishing.
  • If the build node and signing node are separate, credentials are scoped, routing is allowlisted, and cleanup tests pass, then consider production admission.
  • If the signing key has touched a node with unexplained persistence, then stop release dispatch, rotate the affected asset, and rebuild or manually review the node.
  • If a job needs root, broad disk access, or unrestricted network access, then isolate that job and require a separate approval rather than expanding the default runner policy.
  • If a backup Mac cannot recover the same controls after restart or rebuild, then do not treat it as a production fallback.

Run four final tests before approval:

  1. A low-privilege build with no signing material.
  2. A controlled signing job with an approval gate.
  3. A simulated malicious residue test.
  4. A reboot or rebuild recovery test.

The acceptance record should include the repository and group policy, workflow permissions, runner labels, allowed and rejected routing results, Keychain account boundary, cleanup output, network test results, credential rotation path, and recovery owner.

08

When a remote Mac is the safer temporary choice

A self-owned Mac mini can be a good long-term option when the team can operate physical access, storage, account separation, network segmentation, backups, and replacement hardware. It becomes a poor fit when the machine is shared informally, cannot be rebuilt quickly, or mixes development, signing, and production administration.

A remote Mac from MESHLAUNCH can be useful for a non-production validation phase because the team can test a real macOS host without immediately purchasing and permanently dedicating hardware. The correct test is not “does the runner connect?” It is whether the assigned account, reboot behavior, node reset process, and credential separation meet the same acceptance record described above. A Mac mini cloud configuration may fit an isolated build experiment, but signing access should remain disabled until the node passes the team’s own review.

Compared with a casually shared local Mac, the current approach often has three concrete weaknesses: the host may mix unrelated repositories, cleanup may depend on manual habits, and physical or network recovery may be slow when a credential is exposed. Renting a dedicated remote Mac can provide a more controlled temporary test boundary, but it is not a substitute for Runner Group policy, Keychain separation, network restrictions, or evidence-based cleanup.

For teams that need short-lived CI capacity or a controlled trial, use MESHLAUNCH to validate the non-production workflow first. Move signing or release work only after the independent account, restart recovery, node reset, and rejection tests are documented and repeatable.