Separate by client at every layer you can: repositories and organizations, CI secrets, registry tokens, cloud accounts and developer access. Share nothing that would let one compromised credential reach a second client. Where separation is costly, share the lowest risk layer first and keep secrets strictly per client.
The risk in one sentence
An agency is a hub. Staff laptops, CI runners and shared tokens connect to many clients, so an attacker who compromises one of them may reach several customers at once.
Layers to separate
| Layer | Weak setup | Better setup |
|---|---|---|
| Source control | All clients in one organization, broad team access | One organization or account per client, or tightly scoped teams per repo |
| CI secrets | Organization secrets shared with all repositories | Secrets scoped to selected repositories or per environment |
| Registry tokens | One token for every project | One scoped token per client |
| Cloud access | One admin key for all | Separate accounts or projects, short-lived credentials |
| Staff access | Everyone sees everything | Access by assignment, reviewed at project end |
| Laptops | Client code mixed in one folder, shared browser profile | Per client workspaces, full disk encryption, MFA |
What GitHub gives you
GitHub's documentation describes organization secrets with an access policy that can limit use to selected repositories, repository secrets for a single repository, and environment secrets that jobs can read only after protection rules pass. It also recommends OpenID Connect to authenticate to cloud providers where possible, instead of storing long-lived credentials. Secrets are not passed to workflows triggered from forked repositories, except the workflow token.
For outside people such as freelancers, an outside collaborator is someone who is not an organization member but has access to specific repositories. They cannot join teams, and if the organization requires two-factor authentication, they must enable it before accepting. Fork access has to be added separately.
Steps
- List clients, repositories, secrets and who can reach each. A spreadsheet is fine.
- Remove organization-wide secrets. Re-add them at repository or environment level.
- Replace shared tokens with per-client tokens.
- Turn on MFA for every account and enforce it at the organization level.
- Give freelancers access to only the repositories they need, and set an end date.
- Make sure CI runners do not keep state between clients. Prefer fresh hosted runners over a long-lived shared machine, or clean it between jobs.
- Review access at the end of every project. See who should own client hosting, domain and code accounts.
Self-hosted runners
A shared self-hosted runner that builds several clients' code can carry files, caches and credentials from one job to the next. If you must use one, run each job in an isolated container or virtual machine and wipe it after.
Laptops
Client source on a lost laptop is a breach. Require disk encryption, screen locks, a password manager and a remote wipe option. Delete local clones at the end of an engagement.
Check by simulation
Pick a credential at random and ask what it could reach if stolen today. If the answer is more than one client, fix that first.
Quick check
Is one organization per client too much overhead?
It costs setup time but gives the cleanest boundary and an easy handover. If it is too heavy, use per-client teams and per-repository secrets at minimum.
Do we need separate laptops per client?
Usually not. Separate workspaces and strong device controls are the common compromise.
Sources
- GitHub Docs: Using secrets in GitHub Actions
- GitHub Docs: Adding outside collaborators to repositories in your organization
Last reviewed: 19 October 2026.