Machine identities
A machine identity is a login, not a project. It lives under Organization > Access Control > Machine Identities, and it is how anything that is not a person signs in to Infisical.
Your project needs three, and one of them you may already have.
| Identity | Authenticates | Signs in with | Shared across projects |
|---|---|---|---|
| Your machine | local development | Universal Auth | Yes — one per person, not per project |
GH PRs | pull request builds | OIDC | No — bound to one repository |
GH Push | push deploys | OIDC | No — bound to one repository |
Why two of them cannot be shared
Your machine identity is you. You already reach every project you own, so lending it to another one of your own projects crosses no boundary.
The two GitHub identities are not machines. A runner is created and destroyed per job. What the identity represents is code running in one repository on one event, which is why its OIDC subject names the repository:
repo:<owner>/<repo>:ref:refs/heads/{main,development,staging}
Share one across two repositories and anyone who can push a branch to either can read the other's secrets, production included — a workflow runs the file as that branch writes it.
Do you already have these?
Check Organization > Access Control > Machine Identities before creating anything. A second project reuses your machine identity and needs only the two GitHub ones.
Your machine
One per person, not per project. Create it once, then add it to each project you work on.
Create it
Go to Organization > Access Control > Machine Identities, select Create, and name it
after the machine — John Mac, alice-thinkpad. Leave the organization role at member; it only
ever reads secrets inside projects.
Add a client secret
On its page, select Add Client Secret. Infisical shows the Client ID and the Client Secret, and the client secret appears once.
Add it to each project
In every application project, go to Project > Access Control > Machine Identities > Add Machine Identity to Project, and set the Role to Viewer. Repeat this for a second project rather than creating a second identity.
Connect your machines
The same pair goes in every gitignored .env.local, in every project:
INFISICAL_CLIENT_ID=<your client ID>
INFISICAL_CLIENT_SECRET=<your client secret>
Universal Auth covers the mechanism.
GH PRs and GH Push
Two per repository. A runner sets no client pair, falls through to OIDC, and the varlock plugin fetches GitHub's token and exchanges it — so nothing long-lived is stored in your repository.
Create both the same way: Create an identity, then Add Auth Method > OIDC Auth. Where one organization holds more than one project, prefix the names, since identity names are organization-wide.
Read the subject prefix off GitHub rather than typing it. GitHub decides the first half of the
subject, and it is not always repo:<owner>/<repo>:
# Prints the exact prefix GitHub puts in every OIDC token for this repository
gh api /repos/<owner>/<repo>/actions/oidc/customization/sub --jq .sub_claim_prefix
A repository created after 15 July 2026 gets an immutable prefix carrying the owner and repository
IDs — repo:acme@310036262/store@1317816270 — so that renaming either one does not break
authentication, and a deleted repository recreated under the same name inherits nothing. An older
repository prints the plain repo:<owner>/<repo>. Use whichever that command returns, and append the
event part below to it.
| Field | GH PRs | GH Push |
|---|---|---|
| OIDC Discovery URL | https://token.actions.githubusercontent.com | same |
| Issuer | https://token.actions.githubusercontent.com | same |
| Subject | <prefix>:pull_request | <prefix>:ref:refs/heads/{main,development,staging} |
| Audiences | https://github.com/<owner> | same |
| Access Token TTL | 1800 | same |
| Access Token Max TTL | 3600 | same |
| Organization role | member | same |
Then grant each one read on the folder its runs need, through Project > Access Control > Machine Identities > Add Machine Identity to Project:
| Identity | Reads | Because |
|---|---|---|
GH PRs | development:/preview | every pull request is the preview context |
GH Push | development:/development, staging:/, production:/ | a push deploys its branch's Runtime Context |
GH Push needs development as well as the other two, because a push to the development branch is
the most frequent deploy and authenticates as this identity.
Each identity's ID — not its client secret, which OIDC never issues — becomes a repository
variable in First deploy:
INFISICAL_GH_PRS_IDENTITY_ID and INFISICAL_GH_PUSH_IDENTITY_ID.
The subject field accepts glob patterns, where * matches any characters including : and /.
403 Access denied: OIDC subject not allowed — the same message whether the prefix, the event part
or the whole value is wrong. Check the prefix with the command above before changing anything else.
Widen the subject to cover a second repository and that repository can read this one's secrets.OIDC authentication for GitHub covers Infisical's side.
A fourth, if you want CI to plan infrastructure
Optional, and it costs a slot you probably need elsewhere.
infra/tofu.sh authenticates one way — infisical login --method=universal-auth — and a runner
cannot log in interactively, so the infrastructure Plan job needs a client id and secret rather
than an OIDC token. Give it its own identity, granted Viewer on your infrastructure project and
on apps/web's, and store the pair as the INFISICAL_INFRA_ repository secrets.
Skip it and the Plan job skips itself with a warning: a green check that planned nothing. You lose
plan output on infra/** pull requests and nothing else.
apps/web
secret including production's. It is the first identity to drop when you reach the five-identity
limit.Next steps
- Set up your secret store — the projects these identities read
- First deploy — where the two identity IDs are registered
- Environment values — which run picks which identity
