docs/integrations/cicd/githubactions.mdx
This guide walks you through fetching secrets from Infisical inside a GitHub Actions workflow. The Infisical Secrets Action authenticates as a machine identity, fetches the secrets that identity is allowed to read, and injects them into your job as environment variables. The values exist only for the lifetime of the job, so you don't have to copy secrets into GitHub Secrets or keep them in sync there.
<Note> To push secrets from Infisical into GitHub Secrets instead, use [GitHub Secret Syncs](/integrations/secret-syncs/github). Secret Syncs write secrets *to* GitHub, while this guide fetches them *from* Infisical at runtime. </Note> <Tip> Visual learner? Watch an [overview of using Infisical with GitHub Actions](https://www.youtube.com/watch?v=9PQmd_aoRWA). </Tip>Choose how your workflow should authenticate with Infisical:
<Tabs> <Tab title="OIDC (Recommended)"> With [OIDC Auth](/documentation/platform/identities/oidc-auth/github), no Infisical credentials are stored in GitHub. The workflow proves its identity with a token that GitHub issues at runtime.<Accordion title="How OIDC authentication works">
GitHub can vouch for a workflow's identity, which means the workflow doesn't need a stored credential to prove who it is.
When a job starts, GitHub issues a short-lived [OIDC token](https://docs.github.com/en/actions/concepts/security/openid-connect) describing the repository, workflow, and context it came from. The action presents that token to Infisical, Infisical verifies its signature and checks its claims against your machine identity, and then returns a short-lived access token the action uses to fetch secrets.
```mermaid
sequenceDiagram
participant Workflow as GitHub Actions workflow
participant GitHub as GitHub OIDC provider
participant Infisical
Workflow->>GitHub: Request an identity token
GitHub-->>Workflow: Return a signed token describing the repository and context
Workflow->>Infisical: Present the token
Infisical->>GitHub: Fetch the public key to verify the signature
GitHub-->>Infisical: Return the public key
Infisical-->>Workflow: Return a short-lived access token
Workflow->>Infisical: Fetch the secrets the identity can read
```
Because the trust relationship is defined by repository and context rather than by a shared credential, only the workflows you name can authenticate. Use OIDC unless your setup rules it out.
</Accordion>
### Step 1: Create a machine identity
Create a [machine identity](/documentation/platform/identities/machine-identities) for your workflow:
<Steps>
<Step>
In your organization, select **Access Control** > **Machine Identities**.
</Step>
<Step>
Select **+ Create**.
</Step>
<Step>
Enter a **Name** (e.g., `orders-service-ci`), select a **Role**, and select **Create**.
</Step>
</Steps>
Infisical creates the identity with [Universal Auth](/documentation/platform/identities/universal-auth) configured and opens its details page.
### Step 2: Add OIDC authentication
<Steps>
<Step>
In the identity's **Authentication** section, select **+ Add Auth Method**.
</Step>
<Step>
Select **OIDC Auth**.
</Step>
<Step>
On the **Configuration** tab, fill in the fields below, then select **Add**.
</Step>
</Steps>
| Field | Value |
| --- | --- |
| **OIDC Discovery URL** | `https://token.actions.githubusercontent.com` |
| **Issuer** | `https://token.actions.githubusercontent.com` |
| **Subject** | The exact GitHub workflow identity allowed to authenticate. The format depends on whether the repository uses legacy or immutable subject claims, as described below. |
| **Audiences** | The intended recipient of the token. Unless you set `oidc-audience` in your workflow, GitHub issues tokens for the repository owner's URL (e.g., `https://github.com/octo-org`) |
| **Claims** | Optional token claims that must match, entered as property and value pairs |
<Note>
GitHub.com repositories created after July 15, 2026 use immutable subject claims. For these repositories, use `repo:<owner>@<owner-id>/<repo>@<repo-id>:<context>`. Configure the exact subject present in your workflow's token.
If authentication returns a `403`, see the [troubleshooting section](#troubleshooting).
</Note>
The context in the **Subject** field determines how narrowly the identity is scoped. These examples use the legacy subject prefix; for an immutable subject, append the same context to `repo:<owner>@<owner-id>/<repo>@<repo-id>`:
- `repo:octocat/orders-service:ref:refs/heads/main` allows only workflows running on the `main` branch
- `repo:octocat/orders-service:environment:production` allows only workflows running in the `production` environment
- `repo:octocat/orders-service:*` allows any workflow in the repository
<Warning>
- **Subject**, **Audiences**, and **Claims** are each optional, but they won't be checked at all if left blank. We recommend setting the subject and audience at minimum.
- All three of these fields support glob patterns, but any workflow matching the pattern can authenticate to Infisical. For this reason, we recommend using exact values and scoping to a branch or environment rather than the whole repository.
</Warning>
<Tip>
If you're not sure what your workflow's token contains, run [github/actions-oidc-debugger](https://github.com/github/actions-oidc-debugger) in the repository to print its claims, then copy the exact subject into Infisical. Refer to GitHub's [OIDC token reference](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token) for the full list of claims.
</Tip>
### Step 3: Remove Universal Auth
An identity can hold several auth methods, and any of them can authenticate it. Remove Universal Auth to make sure this identity can only authenticate through GitHub:
<Steps>
<Step>
In the identity's **Authentication** section, open the <Icon icon="ellipsis"/> menu on the **Universal Auth** row.
</Step>
<Step>
Select **Remove Auth Method**.
</Step>
<Step>
Enter `confirm`, then select **Remove**.
</Step>
</Steps>
### Step 4: Add the identity to your project
<Steps>
<Step>
On the identity's page, find the **Projects** section and select **+ Add to Project**.
</Step>
<Step>
Select the **Project** containing the secrets your workflow needs, select a **Role** that can read those secrets, and select **Add**.
</Step>
</Steps>
### Step 5: Copy the identity ID and project slug
Copy the values you'll use to configure the action and store them somewhere:
<Steps>
<Step>
At the top of the identity's page, select **Options** > **Copy Machine Identity ID**.
</Step>
<Step>
In the **Projects** section, select the project you added. In the project sidebar, select **Settings** > **General**, then select **Copy Project Slug** in the **Project Overview** section.
</Step>
</Steps>
<Note>
The machine identity ID isn't a secret. It's a public identifier, so you can commit it directly to your workflow file.
</Note>
### Step 6: Add the action to your workflow
Add the Infisical Secrets Action to a workflow file in `.github/workflows/`, before the steps that need secrets:
```yaml expandable
name: Build and push image
on:
workflow_dispatch:
permissions:
id-token: write # Required for GitHub to issue an OIDC token
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Fetch secrets from Infisical
uses: Infisical/[email protected]
with:
method: "oidc"
identity-id: "<machine-identity-id>"
project-slug: "<project-slug>"
env-slug: "dev"
- name: Log in to the registry
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
```
Replace `<machine-identity-id>` and `<project-slug>` with the values you copied, and set `env-slug` to the environment you want to read from (e.g., `dev`, `staging`, `prod`).
<Warning>
The `id-token: write` permission is required. Without it, GitHub won't issue an OIDC token and the step fails before it reaches Infisical.
</Warning>
<Check>
Every step after the action can now read your secrets as environment variables, and no Infisical credentials are stored in GitHub.
</Check>
<Accordion title="How Universal Auth works">
The Infisical Secrets Action reads the client ID and client secret from GitHub Secrets and sends them to Infisical. After Infisical verifies the credentials, it returns a short-lived access token that the action uses to fetch secrets.
```mermaid
sequenceDiagram
participant Workflow as GitHub Actions workflow
participant Infisical
Workflow->>Infisical: Send the client ID and client secret
Infisical-->>Workflow: Return a short-lived access token
Workflow->>Infisical: Fetch the secrets the identity can read
```
Unlike OIDC, Universal Auth requires a stored client secret. Protect this credential in GitHub Secrets and rotate it periodically.
</Accordion>
### Step 1: Create a machine identity
Create a [machine identity](/documentation/platform/identities/machine-identities) for your workflow:
<Steps>
<Step>
Open the project containing the secrets your workflow needs, then select **Access Control** > **Machine Identities**.
</Step>
<Step>
Select **+ Add Machine Identity to Project**.
</Step>
<Step>
Enter a **Name** (e.g., `orders-service-ci`), select a **Role** that can read the secrets your workflow needs, and select **Create**.
</Step>
</Steps>
Infisical creates the identity with Universal Auth configured and opens its details page.
### Step 2: Create a client secret
<Steps>
<Step>
In the identity's **Authentication** section, select the **Universal Auth** row.
</Step>
<Step>
In the **Client Secrets** section, select **+ Add Client Secret**.
</Step>
<Step>
Enter a **Description** (e.g., `github-actions`). Optionally, set the **TTL** and **Max Number of Uses**, then select **Create**.
</Step>
<Step>
Copy the client secret, then select **Close**. Infisical shows the secret only once.
</Step>
</Steps>
### Step 3: Copy the client ID and project slug
Copy the remaining values you'll use to configure the action:
<Steps>
<Step>
In the **Universal Auth** panel, select the <Icon icon="copy"/> button beside the **Client ID**, then close the panel.
</Step>
<Step>
In the project sidebar, select **Settings** > **General**, then select **Copy Project Slug** in the **Project Overview** section.
</Step>
</Steps>
### Step 4: Store the credentials in GitHub
<Steps>
<Step>
In your GitHub repository, select **Settings** > **Secrets and variables** > **Actions**.
</Step>
<Step>
Select **New repository secret**, enter `INFISICAL_CLIENT_ID` as the name and the client ID you copied as the secret, then select **Add secret**.
</Step>
<Step>
Add another repository secret named `INFISICAL_CLIENT_SECRET` with the client secret you copied.
</Step>
</Steps>
<Warning>
Don't put the client ID or client secret in your workflow file. Unlike a machine identity ID, these are credentials that grant access to your project's secrets.
</Warning>
### Step 5: Add the action to your workflow
```yaml
name: Build and push image
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Fetch secrets from Infisical
uses: Infisical/[email protected]
with:
client-id: ${{ secrets.INFISICAL_CLIENT_ID }}
client-secret: ${{ secrets.INFISICAL_CLIENT_SECRET }}
project-slug: "<project-slug>"
env-slug: "dev"
- name: Log in to the registry
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
```
Replace `<project-slug>` with the slug you copied, and set `env-slug` to the environment you want to read from (e.g., `dev`, `staging`, `prod`). Universal Auth is the action's default method, so you don't need to set `method`.
<Check>
Every step after the action can now read your secrets as environment variables.
</Check>
<Tip>
Rotate the client secret periodically, and create a separate identity for each workflow so you can revoke one without affecting the others.
</Tip>
Fetched secrets are available as environment variables to every later step in the same job. Reference them with standard environment variable syntax:
- name: Run tests
run: npm run test -- --database-url="$DATABASE_URL"
By default the action fetches secrets from the root of the environment. Set secret-path to read from a folder, and recursive to include its subfolders:
- name: Fetch secrets from Infisical
uses: Infisical/[email protected]
with:
method: "oidc"
identity-id: "<machine-identity-id>"
project-slug: "<project-slug>"
env-slug: "prod"
secret-path: "/backend"
recursive: true
If your application reads a .env file instead of environment variables, set export-type to file. The path is relative to the workspace, so check out the repository first:
- name: Checkout code
uses: actions/checkout@v4
- name: Fetch secrets from Infisical
uses: Infisical/[email protected]
with:
method: "oidc"
identity-id: "<machine-identity-id>"
project-slug: "<project-slug>"
env-slug: "prod"
export-type: "file"
file-output-path: "/.env"
The action targets Infisical Cloud US by default. Set domain to point it elsewhere:
- name: Fetch secrets from Infisical
uses: Infisical/[email protected]
with:
method: "oidc"
identity-id: "<machine-identity-id>"
project-slug: "<project-slug>"
env-slug: "prod"
domain: "https://eu.infisical.com" # Or your self-hosted instance URL
If your instance uses a certificate from an internal certificate authority, commit the CA certificate to your repository and point the job at it:
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_EXTRA_CA_CERTS: ./ca-certificate.pem
| Input | Default | Description |
|---|---|---|
method | universal | Authentication method: universal, oidc, or aws-iam |
project-slug | Required | Slug of the project to fetch secrets from |
env-slug | Required | Slug of the environment to fetch secrets from |
identity-id | Machine identity ID, required for oidc and aws-iam | |
client-id | Machine identity client ID, required for universal | |
client-secret | Machine identity client secret, required for universal | |
oidc-audience | Custom audience claim for the token GitHub issues | |
secret-path | / | Folder to fetch secrets from |
recursive | false | Also fetch secrets from subfolders of secret-path |
include-imports | true | Include secrets imported into the path |
export-type | env | Inject secrets as environment variables (env) or write them to a file (file) |
file-output-path | /.env | File to write when export-type is file |
domain | https://app.infisical.com | URL of your Infisical instance |
extra-headers | Additional request headers, for instances behind a firewall |
1. Confirm the workflow sets `permissions: id-token: write`. Without it, GitHub never issues a token.
2. Confirm the **Subject** on the machine identity matches the repository and context the workflow actually runs in. A workflow triggered on a pull request or a tag has a different subject than one on `main`.
3. Confirm the **Audiences** value matches the audience in the token, which is the repository owner's URL unless you set `oidc-audience`.
4. Confirm the identity was added to the project, not just created in the organization.
5. Confirm `method` and the matching credential inputs are set. Without `method: "oidc"`, the action defaults to Universal Auth and fails on missing credentials.
For example, the subject changed from:
```text
repo:octocat/orders-service:ref:refs/heads/main
```
To:
```text
repo:octocat@583231/orders-service@1296269:ref:refs/heads/main
```
Renaming or transferring a repository after that date also switches it to the immutable format. Existing repositories can opt in through GitHub's OIDC settings or REST API. GitHub Enterprise Server is unaffected. See [GitHub's immutable subject claims announcement](https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/) for details.
Run `gh api repos/<owner>/<repo>/actions/oidc/customization/sub` and copy the `sub_claim_prefix` value into the machine identity's **Subject** field. A glob pattern such as `repo:octocat@*/orders-service@*:ref:refs/heads/main` can serve as a temporary workaround during migration, but it can also match recycled owner and repository names with different IDs. Use the exact IDs whenever possible.
- `project-slug` matches the slug from **Copy Project Slug**, not the project name
- `env-slug` matches the environment's slug rather than its display name. Slugs are lowercase, typically `dev`, `staging`, and `prod`
- `secret-path` points at the folder holding the secrets, with `recursive: true` if they live in subfolders
- The identity's project role grants read access to that environment and path