Use Case
Secret Management for GitHub Actions
Replace GitHub Actions secrets with vault-backed, policy-controlled credential resolution using KeyZero.
GitHub Actions provides a built-in secrets mechanism: you store key-value pairs in repository or organization settings, and reference them as ${{ secrets.MY_SECRET }} in workflow YAML. This works for simple cases, but as CI/CD pipelines grow in complexity -- especially with AI-assisted workflows -- the limitations become significant.
Problems with GitHub Actions Secrets
Broad Access Scope
GitHub Actions secrets are scoped to the repository or organization level. Every workflow in a repository has access to every repository-level secret. There is no way to say "this secret is only available to the deploy workflow" or "this secret should only be accessible from the main branch" using GitHub's built-in mechanism. Environment-level secrets add some scoping, but environments are coarse-grained and require manual approval gates.
Secrets in Logs
GitHub Actions masks secrets in log output by replacing known values with ***. But this masking is best-effort:
- Secrets transformed by string operations (base64 encoding, URL encoding, substring extraction) are not masked.
- Secrets printed by third-party actions or scripts that format output differently may slip through.
- Multi-line secrets are masked line by line, and partial matches may not trigger masking.
Artifact Leakage
Workflow artifacts, test reports, and coverage files are often uploaded without sanitization. A test that logs a database URL or a debug step that dumps environment variables can embed credentials in artifacts that persist long after the workflow completes.
No Audit Trail for Individual Secrets
GitHub provides audit logs for secret creation and deletion, but not for which workflow run accessed which secret. You know that PROD_DB_PASSWORD was used by the repository, but not whether it was the deploy workflow, the test suite, or a PR from a fork.
How KeyZero Works in GitHub Actions
KeyZero integrates with GitHub Actions through its JWT identity support and kz run CLI. GitHub Actions provides an OIDC token to each workflow run -- KeyZero verifies this token and evaluates policies before resolving secrets.
Step 1: Install KeyZero in Your Workflow
- name: Install KeyZero
run: |
curl -fsSL https://get.keyzero.dev | sh
echo "$HOME/.keyzero/bin" >> $GITHUB_PATH
Step 2: Configure the JWT Issuer
In your KeyZero bundle, configure GitHub Actions as a JWT issuer:
# bundle.yaml
issuers:
- name: github
type: github-actions
issuer_url: "https://token.actions.githubusercontent.com"
audience: "keyzero"
The github-actions issuer type automatically maps GitHub's OIDC claims to standard identity fields:
| GitHub Claim | KeyZero Identity Field |
|---|---|
repository | identity.service |
ref | identity.env |
workflow_ref | identity.workflow |
actor | identity.user |
Step 3: Write CEL Policies
CEL (Common Expression Language) policies control which workflows can access which secrets:
# bundle.yaml
policies:
- match: "secret/data/prod/**"
rules:
- cel: >
identity.service == 'myorg/myapp'
&& identity.env == 'refs/heads/main'
effect: allow
mode: direct
- match: "secret/data/staging/**"
rules:
- cel: >
identity.service == 'myorg/myapp'
&& identity.env.startsWith('refs/heads/')
effect: allow
mode: direct
- match: "secret/data/dev/**"
rules:
- cel: "identity.service.startsWith('myorg/')"
effect: allow
mode: direct
This gives you fine-grained control: production secrets are only available to the main branch of myorg/myapp. Staging secrets are available to any branch. Dev secrets are available to any repo in the org. Learn more about this approach in policy-based access control for machine identities.
Step 4: Use kz run in Your Workflow
name: Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install KeyZero
run: |
curl -fsSL https://get.keyzero.dev | sh
echo "$HOME/.keyzero/bin" >> $GITHUB_PATH
- name: Deploy with secrets
run: |
kz run \
--bundle ./bundle.yaml \
--server https://keyzero.internal.myorg.com \
-- ./deploy.sh
env:
KZ_JWT: ${{ steps.auth.outputs.token }}
The deploy.sh script receives environment variables resolved from Vault (or AWS SM, 1Password, etc.) based on the policy evaluation. The secrets never appear in the workflow YAML, and access is controlled per-workflow, per-branch, per-repository. This eliminates the hardcoded secrets problem that plagues many CI/CD pipelines. For backend-specific setup, see the HashiCorp Vault and AWS Secrets Manager integration guides.
Comparison with Native GitHub Secrets
| Feature | GitHub Actions Secrets | KeyZero |
|---|---|---|
| Storage backend | GitHub | Vault, AWS SM, 1Password, GCP SM, etc. |
| Access scope | Repository / Organization / Environment | CEL policy per secret path |
| Branch restrictions | Environment protection rules | CEL expression on identity.env |
| Audit trail | Secret CRUD events | Per-resolution audit log |
| Secret rotation | Manual update in GitHub UI | Automatic via backend |
| AI workflow support | No special handling | Blind mode, fetch tool |
AI-Assisted CI/CD
If your GitHub Actions workflows use AI agents (e.g., automated code review, AI-powered testing, or LLM-based deployment validation), KeyZero's blind mode prevents credentials from entering agent context windows during CI runs:
- name: AI-assisted review
run: |
kz run --blind \
--bundle ./bundle.yaml \
-- python ai_review.py
The AI review script can access APIs through opaque tokens. If it logs or outputs credential values, only the opaque tokens appear -- not the real credentials. This is critical for workflows that send CI context to LLM providers.