Appearance
Secrets Management
Secure handling of sensitive configuration data across all Jubiloop environments.
Overview
Jubiloop uses a multi-layered approach to secrets management:
- GitHub Secrets - Primary storage for CI/CD secrets
- Environment Variables - Runtime injection of secrets
- 1Password - Team access and secret sharing
- Automated Discovery - Scripts to identify required secrets
Secret Storage
GitHub Secrets
All deployment secrets are stored in GitHub:
- Repository-level secrets
- Environment-specific secrets
- Encrypted at rest
- Access controlled by GitHub permissions
Adding Secrets:
bash
# Via GitHub UI
Settings → Secrets and Variables → Actions → New repository secret
# Secret naming convention
{ENVIRONMENT}_{SERVICE}_{KEY_NAME}1Password Vault
Team secrets are documented in 1Password:
- Jubiloop vault for shared access
- Environment-specific sections
- Secure notes for context
- API keys and credentials
Secret Types
Application Secrets
Database Credentials
- PostgreSQL passwords
- Redis passwords
- Connection strings
API Keys
- Third-party service keys
- OAuth credentials
- Service-specific signing secrets
Signing And Session Secrets
- Application and Better Auth secrets
- Encryption passwords
Infrastructure Secrets
Cloud Provider
- DigitalOcean API tokens
- Cloudflare API keys
- Terraform state credentials
Deployment
- SSH private keys
- Container registry tokens
Secret Patterns
Auto-Generated Names
Using from_secrets in configuration:
yaml
DEV:
server:
APP_KEY: 'from_secrets'
# Generates: DEV_SERVER_APP_KEYExplicit References
For shared secrets between services:
yaml
DEV:
server:
DB_PASSWORD: '${{ secrets.DEV_POSTGRES_PASSWORD }}'
postgres:
POSTGRES_PASSWORD: '${{ secrets.DEV_POSTGRES_PASSWORD }}'Secret Discovery
List Required Secrets
bash
cd infra/deploy
# Deployment configuration secrets from env.deploy.yml
node scripts/extract-config.js list-secretslist-secrets reports secrets referenced by env.deploy.yml, including generated names. It does not include CI-only workflow secrets and does not support environment filtering.
Verify Secret Configuration
bash
# Check missing secrets
node scripts/generate-env-files.js DEV --list-secrets
# Generate documentation
node scripts/extract-config.js documentationSecurity Best Practices
Secret Generation
Strong Passwords:
bash
# Generate secure password
openssl rand -base64 32
# Generate hex string
openssl rand -hex 32API Key Format:
- Minimum 32 characters
- Include special characters
- Avoid predictable patterns
Access Control
Principle of Least Privilege:
- Only necessary team members
- Environment-specific access
- Regular access reviews
Secret Sharing:
- Never share via chat/email
- Use 1Password for sharing
- Document access in team wiki
Audit and Compliance
Secret Usage Tracking:
- GitHub audit logs
- Deployment logs
- Access monitoring
Regular Audits:
- Quarterly secret review
- Access permission audit
- Unused secret cleanup
Common Secret Patterns
Database URLs
yaml
# Pattern
DATABASE_URL: 'postgresql://user:${{ secrets.DB_PASS }}@host:5432/db'
# Separate components
DB_HOST: 'localhost'
DB_USER: 'app_user'
DB_PASSWORD: '${{ secrets.DEV_DB_PASSWORD }}'
DB_NAME: 'jubiloop_dev'Sanity Credentials
Marketing and Studio configuration currently require SANITY_STUDIO_API_TOKEN. Sanity makes SANITY_STUDIO_* variables available to the Studio browser bundle, so use a read-only token with the minimum project and dataset access needed for content queries. Never use an administrator, editor, or deploy token for this value. Store environment values in GitHub Secrets and 1Password.
The marketing workflow uses a separate environment-specific Studio deploy token after the Worker deploy succeeds:
| Environment | Token to use |
|---|---|
| Development | Development Sanity Studio deploy token |
| QA | QA Sanity Studio deploy token |
| Production | Production Sanity Studio deploy token |
bash
cd apps/marketing
SANITY_AUTH_TOKEN=<token-from-1password> \
pnpm exec sanity deploy -yUse the command only as an approved fallback. The deploy token is a CI/CLI credential, not a marketing runtime credential. Never assign it to a SANITY_STUDIO_* variable. Before deploying, set the environment-specific project ID, dataset, and Studio host. Do not place either token in a tracked file. Verify the hosted Studio after automatic or fallback deployment.
Discord Notification Credentials
Store each channel's incoming webhook URL as a repository secret:
| Secret | Discord channel |
|---|---|
DISCORD_DEV_NOTIFICATIONS_WEBHOOK_URL | 🛠️・ops-dev |
DISCORD_QA_DEPLOYMENTS_WEBHOOK_URL | 🧪・ops-qa |
DISCORD_PROD_DEPLOYMENTS_WEBHOOK_URL | 🚀・ops-production |
GitHub Actions injects each secret into its notification action step through the DISCORD_WEBHOOK_URL environment variable. Webhook URLs aren't passed as action inputs and aren't application runtime credentials, so don't add them to infra/deploy/env.deploy.yml or an app's environment configuration.
Discord role IDs aren't secrets. Store the development reviewers role ID in DISCORD_DEV_REVIEWERS_ROLE_ID and the operations maintainers role ID in DISCORD_PROD_MAINTAINERS_ROLE_ID as repository variables. The operations role receives separate failure alerts in both QA and production channels, so it needs access to both. Store the development event and mention switches as repository variables too; unset switches are false. See the CI/CD pipeline guide for the full variable list and behavior.
If a webhook URL is exposed, delete the webhook in Discord, create a replacement in the same channel, and update only the matching GitHub secret.
Troubleshooting
Secret Not Found
- Check exact secret name (case-sensitive)
- Verify secret exists in GitHub
- Check environment spelling
- Review deployment logs
Invalid Secret Format
- Check for special characters
- Verify encoding (base64 if needed)
- Check length requirements
- Test locally with dry-run
Access Denied
- Verify GitHub permissions
- Check environment access
- Contact team admin
- Review audit logs
Rotation
- Create a replacement at the provider without revoking the active credential when overlap is supported.
- Update 1Password and every matching GitHub secret.
- Redeploy each consumer and verify it uses the replacement.
- Revoke the old credential and record the rotation date and owner in 1Password.
For signing or session secrets that invalidate active sessions, schedule the change and communicate the user impact before redeployment.
Compromised Secret
- Revoke or disable the exposed credential immediately.
- Identify every environment, repository setting, 1Password item, host, and dependent credential that contains or trusts it.
- Generate replacements and update GitHub and 1Password without writing values to logs or tracked files.
- Redeploy every consumer and verify service health.
- Review provider, GitHub, deployment, and application logs for misuse.
- Record the exposure window, affected systems, response, and follow-up work through the team's incident process.
Documentation
Required Documentation
For each secret:
- Purpose and usage
- Format requirements
- Source (if external service)
- Dependencies
Secret Inventory
Maintain inventory in 1Password:
- Service name
- Environment
- Last rotation date
- Owner/contact