GitHub Secrets React: Secure Environment Variables
GitHub Secrets are encrypted environment variables stored in your repository. They're used to securely pass sensitive data (API keys, database credentials, deployment tokens) to workflows without exposing secrets in code or logs. GitHub encrypts secrets at rest and redacts them in workflow logs, ensuring that even if your workflow runs publicly, secrets remain hidden.
Why Secrets Matter
Hardcoding credentials in code is a critical security vulnerability. If you commit an API key to git, even in a private repo, anyone with repository access can steal it. If you accidentally push secrets to a public repository, automated scrapers harvest them within minutes. GitHub scans public repositories for secrets and notifies users when exposed credentials are detected.
The 2024 Software Supply Chain Report found that 25% of breaches involved compromised credentials in CI/CD systems. Using GitHub Secrets prevents this by ensuring credentials are never stored in code, logs, or version control.
Creating and Managing Secrets
Repository Secrets
Create secrets at the repository level (Settings > Secrets and variables > Actions):
- Go to Settings > Secrets and variables > Actions
- Click New repository secret
- Name:
API_KEY - Value: paste your secret
- Click Add secret
Once created, the secret is encrypted and can't be viewed or exported. You can only rotate it by overwriting.
Common repository secrets:
REACT_APP_API_KEY # API key for your backend
DATABASE_URL # Database connection string
DEPLOY_TOKEN # Deployment service token
AWS_ACCESS_KEY_ID # AWS credentials
AWS_SECRET_ACCESS_KEY # AWS credentials
Organization Secrets
For teams, create organization-level secrets (Settings > Secrets > Actions) that are shared across all repositories in the organization. This simplifies management when the same credentials are used across multiple projects.
Using Secrets in Workflows
Reference secrets with ${{ secrets.SECRET_NAME }}. GitHub automatically redacts secret values from logs:
name: Build and Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build React app
env:
REACT_APP_API_KEY: ${{ secrets.REACT_APP_API_KEY }}
REACT_APP_ENV: production
run: npm run build
- name: Deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: npm run deploy
In this example:
REACT_APP_API_KEY: Injected from GitHub Secrets at build timeDEPLOY_TOKEN: Used by the deploy script- Build-time secrets (prefixed with
REACT_APP_) are baked into the JavaScript bundle (see warning below)
Critical Warning: Build-Time vs. Runtime Secrets
REACT_APP_* secrets are embedded in your built JavaScript and visible in the browser. Never use them for highly sensitive credentials:
// UNSAFE: Never do this
const API_KEY = process.env.REACT_APP_DATABASE_PASSWORD;
Use build-time secrets only for:
- Public API keys (with rate limiting)
- Feature flags
- Service endpoints
For sensitive credentials (database passwords, signing keys, etc.), use runtime environment variables on your server, not in the browser.
Managing Multiple Environments
Use environment-specific secrets for staging and production:
name: Deploy
on:
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, production]
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to ${{ github.event.inputs.environment }}
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "Deploying to ${{ github.event.inputs.environment }}"
npm run deploy -- --env ${{ github.event.inputs.environment }}
Configure environment-specific secrets in Settings > Environments > [environment name] > Environment secrets. The environment key in the workflow selects which secrets are available.
Secrets Security Best Practices
| Practice | Why |
|---|---|
| Rotate secrets quarterly | Limits blast radius if compromised |
| Use minimal-permission tokens | A deploy token needs fewer permissions than a full PAT |
| Scope secrets to environments | Staging secrets shouldn't deploy to production |
| Audit secret access | GitHub logs all secret reads |
| Use short-lived tokens | Some services (AWS, Salesforce) support temporary tokens |
Auditing Secret Usage
GitHub logs every workflow that accesses a secret. Check the Security tab > Secret scanning to see access patterns. GitHub also alerts if a secret is exposed in code.
Handling Secrets Rotation
If a secret is compromised:
- Rotate immediately: Create a new token in your service (e.g., AWS, API provider)
- Update GitHub: Go to Settings > Secrets and update the value
- Audit logs: Check GitHub and your service logs for unauthorized access
- Verify integrity: Ensure your application is still functioning correctly
Example rotation checklist:
# .github/workflows/rotate-secrets.yml
# Run manually when secrets expire or are compromised
name: Rotate Secrets
on:
workflow_dispatch:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Verify current secrets work
env:
API_KEY: ${{ secrets.API_KEY }}
run: |
curl -H "Authorization: Bearer ${API_KEY}" \
https://api.example.com/health
- name: Create issue for manual rotation
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Rotate GitHub Secrets',
body: '1. Generate new API keys in service\n2. Update GitHub secrets\n3. Verify deployment works',
labels: ['devops', 'security']
});
Comparing Secret Management Approaches
| Approach | Security | Convenience | Cost |
|---|---|---|---|
| GitHub Secrets | Good | High (native integration) | Free |
| AWS Secrets Manager | Excellent | Medium (API calls) | ~$0.40/secret/month |
| HashiCorp Vault | Excellent | Low (self-hosted) | Self-hosted |
| 1Password | Good | High (UI + API) | ~$3-5/person/month |
For most React CI/CD pipelines, GitHub Secrets is sufficient. For enterprise security requirements, consider AWS Secrets Manager or Vault.
Key Takeaways
- GitHub Secrets are encrypted environment variables that prevent credential exposure in code and logs
- Secrets are automatically redacted from workflow logs, even if accidentally printed
- Use environment-specific secrets to prevent staging credentials from being deployed to production
- Never put sensitive credentials in build-time environment variables (like
REACT_APP_*) that end up in the browser - Rotate secrets quarterly and immediately if compromised
- Audit secret access through GitHub's Security tab
Frequently Asked Questions
What if I accidentally commit a secret?
GitHub's Secret Scanning automatically detects exposed credentials in public repositories and notifies the service provider. If you commit a secret:
- Immediately rotate the credential in your service
- Remove it from git history:
git filter-branchorBFG Repo-Cleaner - Force push (if you own the repo):
git push --force-with-lease
Can I use multiple secrets in one step?
Yes, use multiple env variables:
- name: Deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run deploy
What's the difference between repository and organization secrets?
Repository secrets are accessible only in that repository. Organization secrets are shared across all repositories in the organization, which simplifies management for teams. Use organization secrets for shared credentials (database, API keys) and repository secrets for repo-specific tokens.
How do I know if a secret was accessed?
Check Settings > Security > Secret scanning. GitHub logs all secret reads. You can also audit by reviewing workflow runs and checking which jobs accessed each secret.