Back to Infisical

Inject secrets into GitHub Actions workflows

docs/integrations/cicd/githubactions.mdx

0.162.2422.3 KB
Original Source

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>

Prerequisites

Setup

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>
</Tab> <Tab title="Universal Auth"> Use [Universal Auth](/documentation/platform/identities/universal-auth) when OIDC isn't an option, such as on a runner that can't request an identity token. The workflow authenticates with a client ID and client secret stored in GitHub Secrets.
<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>
</Tab> </Tabs> <Note> If your workflow runs on a self-hosted runner in AWS, the action can also authenticate with [AWS Auth](/documentation/platform/identities/aws-auth) by setting `method: "aws-iam"` and `identity-id`. The runner needs AWS credentials it can use and network access to the AWS STS endpoints. </Note>

Configure the action

Reference secrets in later steps

Fetched secrets are available as environment variables to every later step in the same job. Reference them with standard environment variable syntax:

yaml
- name: Run tests
  run: npm run test -- --database-url="$DATABASE_URL"
<Warning> The action registers each fetched value as a masked secret, so GitHub redacts it from logs. Masking is a safety net rather than a guarantee: transformed values, such as a secret that a tool base64-encodes or embeds in a URL, can still appear in output. Don't print secrets. </Warning>

Fetch from a specific folder

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:

yaml
- 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

Write secrets to a file

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:

yaml
- 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"
<Warning> Values written to a file aren't masked in logs, and the file stays in the workspace for the rest of the job. Don't commit it or upload it as a build artifact. </Warning>

EU Cloud and self-hosted instances

The action targets Infisical Cloud US by default. Set domain to point it elsewhere:

yaml
- 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:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      NODE_EXTRA_CA_CERTS: ./ca-certificate.pem
<Note> If your instance sits behind a header-based firewall, pass the required headers with `extra-headers`, one `Header-Name: value` pair per line. </Note>

Action inputs

InputDefaultDescription
methoduniversalAuthentication method: universal, oidc, or aws-iam
project-slugRequiredSlug of the project to fetch secrets from
env-slugRequiredSlug of the environment to fetch secrets from
identity-idMachine identity ID, required for oidc and aws-iam
client-idMachine identity client ID, required for universal
client-secretMachine identity client secret, required for universal
oidc-audienceCustom audience claim for the token GitHub issues
secret-path/Folder to fetch secrets from
recursivefalseAlso fetch secrets from subfolders of secret-path
include-importstrueInclude secrets imported into the path
export-typeenvInject secrets as environment variables (env) or write them to a file (file)
file-output-path/.envFile to write when export-type is file
domainhttps://app.infisical.comURL of your Infisical instance
extra-headersAdditional request headers, for instances behind a firewall
<Tip> Pin the action to a specific release rather than a branch so your workflows stay reproducible. Check the [releases page](https://github.com/Infisical/secrets-action/releases) for the current version. </Tip>

Troubleshooting

<AccordionGroup> <Accordion title="Authentication fails with an access denied error"> Work through these in order:
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.
</Accordion> <Accordion title="OIDC authentication fails with a 403 for a new or renamed repository"> GitHub.com repositories created after July 15, 2026 use immutable subject claims. The repository owner and name are suffixed with their permanent numeric IDs, so a subject configured with the previous format won't match and Infisical returns a `403` error.
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.
</Accordion> <Accordion title="The action succeeds but my secrets are missing"> The action only injects what the identity can read from the path you requested. Check that:
- `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
</Accordion> <Accordion title="How do I see what claims GitHub is sending?"> Run [github/actions-oidc-debugger](https://github.com/github/actions-oidc-debugger) in the repository to print the token's claims, then compare them against the subject, audience, and claims configured on your machine identity. This is the fastest way to resolve a subject mismatch. </Accordion> <Accordion title="Can one job pull secrets from several environments or folders?"> Yes. Add the action once for each source. When two sources define the same key, the later step's value wins, so order the steps accordingly. If you export to files, give each step a different `file-output-path`. </Accordion> </AccordionGroup>

Next steps

<CardGroup cols={2}> <Card title="OIDC Auth" icon="key" href="/documentation/platform/identities/oidc-auth/github"> Review the full set of OIDC configuration options for GitHub. </Card> <Card title="Machine Identities" icon="robot" href="/documentation/platform/identities/machine-identities"> Understand how identities authenticate and what they can access. </Card> <Card title="GitHub Secret Syncs" icon="arrows-rotate" href="/integrations/secret-syncs/github"> Push secrets from Infisical into GitHub Secrets instead. </Card> <Card title="Secrets Delivery" icon="truck-fast" href="/documentation/platform/secrets-mgmt/concepts/secrets-delivery"> Compare delivery methods across CI/CD, Kubernetes, and applications. </Card> </CardGroup>