Well-Architected Framework
Secure GitHub Actions secrets with HashiCorp Vault
GitHub Actions has native OIDC token issuance built into the platform. Jobs can
request a signed JWT from GitHub's OIDC provider when the workflow grants
id-token: write permission. The token contains claims that identify what is
running, including the repository, branch or tag reference, deployment
environment, and workflow file.
Architecture
The action-to-Vault authentication flow is based on GitHub-issued OIDC tokens:
- Runner requests a JWT from GitHub's OIDC provider (
token.actions.githubusercontent.com) — requiresid-token: writedeclared on the job - GitHub issues a JWT containing claims for the repository, branch, environment, and workflow file
- Runner sends the JWT to Vault's JWT auth endpoint
- Vault fetches signing keys from GitHub's shared OIDC discovery URL and validates the token signature
- Vault evaluates
bound_claims— repository, environment, ref, orjob_workflow_ref— against the configured role - Vault issues a short-lived token scoped to the policies attached to the matching role
The runner uses the Vault token to retrieve the secrets authorized for that role.

Runner hosting and auth method selection
The right auth method depends on where GitHub Actions runners execute. JWT/OIDC
auth works for all runner types because GitHub issues an OIDC token for any job
with id-token: write permission, regardless of the underlying infrastructure.
However, when jobs run on infrastructure you control, additional auth methods
become available that can provide stronger identity guarantees or remove the
dependency on reaching GitHub's OIDC endpoint.
- GitHub-hosted runners — JWT/OIDC is the only practical option. You do not control the underlying infrastructure, so there is no instance or pod identity to leverage.
- Self-hosted runners on Kubernetes (Actions Runner Controller) — Kubernetes auth is available in addition to JWT. The runner pod's service account token is verified directly by the cluster API, independent of GitHub.
- Self-hosted runners on AWS, Azure, or GCP — Cloud provider auth methods (AWS IAM, Azure Managed Identity, GCP IAM) are available. The runner instance already has a platform identity that Vault can validate against the cloud provider's API — no credential to provision or rotate.
- Self-hosted runners on bare metal or air-gapped environments — AppRole
with response wrapping, or TLS certificate auth for persistent runner fleets.
In air-gapped environments where GitHub's OIDC endpoint
(
token.actions.githubusercontent.com) is unreachable, AppRole becomes the required fallback.
Authentication options
Because GitHub Actions has native OIDC token issuance, use JWT/OIDC auth for all GitHub-hosted runners and internet-connected self-hosted runners. GitHub's per-job OIDC tokens are the purpose-built mechanism for the Vault integration. The tokens provide the following benefits:
- No long-lived, static credentials
- Fine-grained
bound_claimsthat can restrict authentication to a specific repository, branch, deployment environment, or workflow file
The hashicorp/vault-action GitHub Action wraps the OIDC exchange and secret retrieval into a single workflow step. The security controls live on the Vault side, in how roles bind to GitHub's JWT claims — not in GitHub itself.
Depending on your use case, there are additional auth methods that may be appropriate for GitHub Actions workflows in your environment. For example, if using self-hosted runners on Kubernetes managed by Actions Runner Controller, use the Kubernetes auth method. The pod's service account token is more authoritative than the GitHub JWT in that context and doesn't require the runner to reach GitHub's OIDC endpoint.
The following table summarizes when each auth method fits best.
| Auth method | Security posture | Credential lifecycle | Best suited for |
|---|---|---|---|
| JWT / OIDC | High; per-job identity bound to GitHub repository, environment, and workflow metadata | Token issued per job by GitHub, no rotation needed | GitHub-hosted runners and internet-connected self-hosted runners |
| Cloud provider auth methods | Highest for cloud-hosted runners; identity is the platform infrastructure itself | Managed by cloud provider; no rotation required | Self-hosted runners on EC2, Azure VMs, or GCE instances with an assigned instance identity |
| Kubernetes | Highest for in-cluster; service account token verified directly by the cluster API | Short-lived projected service account token, auto-rotated by Kubernetes | Self-hosted runners managed by Actions Runner Controller on Kubernetes |
| TLS Certificate | High; mutual authentication | Certificate with defined expiry; requires rotation process | Long-lived, persistent runner fleets where mTLS is part of the existing security posture |
| AppRole | Moderate; requires secure secret ID delivery | Secret ID is single-use with response wrapping; RoleID is long-lived | Air-gapped self-hosted runners where neither cloud identity nor JWT is available |
JWT auth is the right choice for most GitHub Actions workflows, especially if using GitHub-hosted runners or self-hosted runners that can access GitHub's OIDC endpoint.
GitHub's OIDC token contains a rich set of claims that Vault can use as binding conditions, including:
| Claim | Example value | Use for |
|---|---|---|
sub | repo:org/repo:environment:production/main | Restrict to specific repository and branch |
repository | org/repo | Restrict to specific repository |
environment | production | Restrict to specific deployment environment |
ref | refs/heads/main | Restrict to specific branch |
job_workflow_ref | org/repo/.github/workflows/deploy.yml | Restrict to specific workflow file |
Role configuration
The Vault role's bound_claims are where you translate these into access
controls. An unconstrained role that accepts any JWT from GitHub's OIDC
issuer is like giving every repository in your organization access to
those secrets. Use bound_subject for exact matches, or bound_claims with
bound_claims_type: glob for pattern matching.
# Vault role — scoped to a specific repo and deployment environment
# bound_subject encodes: repo identity + environment context
vault write auth/jwt/role/github-actions-deploy \
role_type=jwt \
bound_audiences="https://github.com/your-org" \
bound_subject="repo:your-org/your-repo:environment:production" \
user_claim=repository \
token_policies=deploy-production-policy \
ttl=15m
The role binds Vault access to a specific repository and deployment environment. Vault then issues a short-lived token only when the workflow presents the expected GitHub identity.
Workflow configuration
The workflow requires id-token: write permission to request a JWT from GitHub's OIDC provider. Scope this to the specific job that needs Vault access rather than the entire workflow — granting it at the workflow level gives every job the ability to request OIDC tokens.
jobs:
deploy:
runs-on: ubuntu-latest
# Scope permissions to this job only, not the whole workflow
permissions:
id-token: write # Required: allows this job to request a GitHub OIDC token
contents: read
steps:
- uses: actions/checkout@v4
- name: Retrieve secrets from Vault
uses: hashicorp/vault-action@v3
with:
url: https://vault.example.com
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: jwt
role: github-actions-deploy
# jwtGitHubAudience customizes the aud claim — coordinate with Vault team
# if you change this from the default
secrets: |
secret/data/ci/production/db password | DB_PASSWORD ;
secret/data/ci/production/api key | API_KEY
The workflow requests a GitHub OIDC token only for the job that needs Vault access. The Vault GitHub Action then exchanges that token for a Vault token and retrieves only the secrets required for the deployment.
Policy
The Vault policy for any role should follow least privilege. A pipeline that deploys to production should not be able to read staging secrets, and a staging pipeline should not be able to read production secrets.
An example Vault policy for a production deployment pipeline might look like this:
# Vault policy: scope to exactly the paths this pipeline needs
path "secret/data/ci/production/db" {
capabilities = ["read"]
}
# Deny access to everything else implicitly
The policy limits the workflow to the exact production secret path it needs. Use separate policies for other environments so unrelated workflows do not inherit broader access.
Security considerations
There are several security considerations you need to align with your organizational risk tolerance and operational constraints when designing your GitHub Actions secrets management strategy:
- JWT role constraints: Bind Vault roles as tightly as possible with
bound_subject,bound_claims, or both. A role that accepts any valid token from GitHub's OIDC issuer can allow unintended repositories or workflows to authenticate. - Job-level token permissions: Grant
id-token: writeonly to the job that needs Vault access. If you grant it at the workflow level, every job in that workflow can request an OIDC token. - Workflow-level scoping: In repositories with multiple teams or deployment
targets, bind roles to specific workflow files with
job_workflow_ref. Without that restriction, any workflow in the repository withid-token: writemay be able to authenticate to a repository-scoped role.
For a list of additional security considerations, refer to the CI/CD security considerations page.
Integrate HCP Vault Radar
Before migrating to Vault-based secrets management, Vault Radar can scan existing GitHub Actions workflow files and connected repositories for credentials stored as hardcoded values. This is particularly important in organizations that have accumulated years of workflows with credentials stored across multiple GitHub organizations, environments, and secret scopes.
After migration, Vault Radar can monitor repositories continuously for new secret introductions — catching cases where developers hardcode credentials that should be going through Vault.
Refer to the HCP Vault Radar quick start tutorials to learn about HCP Vault Radar.
Use Vault secrets sync for GitHub Secrets
Vault Secrets Sync provides an alternative solution for managing GitHub Actions pipeline secrets that is useful in cases where using the GitHub OIDC provider is not a feasible approach. Vault Secrets Sync simplifies configuration and lets you to select specific secrets or secret paths for synchronization from Vault to GitHub as repository or environment secrets.
When you use this approach you gain several benefits:
- Centralized policy and audit controls
- Repository-level access control
- No need to include authentication logic in your workflow
- Compatibility with all runner types without extra configuration
- Synchronization to GitHub when you rotate secrets in Vault
- Access to secrets through familiar GitHub Actions syntax without needing to learn Vault details
The Secrets Sync approach has the following limitations:
- Supports only static key/value (KV) Vault secrets engines.
- Copies secrets into GitHub instead of just-in-time access.
- Can require extra management of GitHub Personal Access Tokens or App Tokens for authentication.
The recommended approach, when possible, is to access Vault directly from your GitHub Actions workflows using the Vault GitHub Action and GitHub's OIDC tokens.
Direct access provides the strongest security posture and the best secrets management experience. Use Vault Secrets Sync for GitHub Secrets when the direct integration is not feasible due to technical constraints or organizational policies.
HashiCorp resources
Vault
- Read Retrieve Vault secrets from GitHub Actions for a validated architecture pattern for GitHub Actions and Vault.
- Watch Using OIDC With HashiCorp Vault and GitHub Actions for a video walkthrough of the OIDC-based integration.
- Watch Building Scalable Enterprise Secrets Management with GitHub OIDC and HashiCorp Vault for enterprise-scale patterns for GitHub OIDC and Vault.
Vault Secrets Sync
- Watch Developer's Guide to HCP Vault, Part 3: Secrets sync for an introduction to syncing Vault secrets to GitHub Actions.
- Watch Vault Enterprise secrets sync (beta) for an overview of the Vault Enterprise secrets sync feature.
Vault GitHub Action
- Read Vault GitHub Action to install and configure the official HashiCorp Vault GitHub Action.
- Read the Vault GitHub Action repository for source code and configuration options.
- Follow the Automate workflows with Vault GitHub actions tutorial to integrate Vault into GitHub Actions workflows.
- Read Integrate with GitHub Actions to connect HCP Vault Secrets to GitHub Actions.
- Read Learn Vault GitHub Actions for example code to get started with the Vault GitHub Action.
- Watch Secure Developer Workflows with Vault & GitHub Actions for a video demonstration of securing developer workflows with Vault.
- Watch Secure GitOps Workflows with GitHub Actions and HashiCorp Vault for a GitOps-focused walkthrough of Vault and GitHub Actions.
External resources
- Read Configuring OpenID Connect in HashiCorp Vault for GitHub's official documentation on OIDC configuration for Vault.
- Read HashiCorp Vault Secrets Sync Demo for example code demonstrating Vault secrets sync with GitHub.
- Read HashiCorp Vault GitHub Secrets Sync with Terraform to learn how to manage Vault secrets sync with Terraform.
- Read Push button security for your GitHub Actions for HashiCorp's guide to securing GitHub Actions pipelines.
- Read How to Use HashiCorp Vault Action for a practical guide to the Vault GitHub Action.
- Read Vault GitHub Actions example for a complete example workflow using the Vault GitHub Action.
- Watch Automate Workflows w/ HashiCorp Vault GitHub Actions for a video demonstration of automating workflows with the Vault GitHub Action.
Next steps
In this section of managing CI/CD secrets, you learned how to bind GitHub Actions workload identity to Vault auth methods and how to scope secrets to specific repositories, environments, and workflow files. Secure GitHub Actions secrets with HashiCorp Vault is part of the Secure systems pillar.