Bun Secrets Management
Universal Release uses Bun's Bun.secrets API for secure credential storage.
Why Bun.secrets?β
- OS-level encryption - Uses system keychains
- Memory zeroing - Sensitive data cleared from memory
- No environment leaks - Secrets not visible in process listings
- Automatic rotation - Update without restart
Platform Supportβ
- macOS: Keychain Services
- Linux: libsecret (GNOME Keyring, KWallet)
- Windows: Credential Manager
CLI Usageβ
Store a Secretβ
release secrets set NPM_TOKEN
Enter value: ***************
β Secret "NPM_TOKEN" stored securely
Retrieve a Secretβ
release secrets get NPM_TOKEN
npm_xxxxxxxxxxxxxxxxxxxx
Check if Secret Existsβ
release secrets has NPM_TOKEN
β Secret "NPM_TOKEN" exists
Delete a Secretβ
release secrets delete NPM_TOKEN
β Secret "NPM_TOKEN" deleted
Export to .env Formatβ
release secrets export NPM_TOKEN CARGO_TOKEN
NPM_TOKEN="npm_..."
CARGO_TOKEN="..."
Programmatic Usageβ
import { secrets } from "./utils/secrets.ts";
// Store in OS keychain
await secrets.set("NPM_TOKEN", "npm_...");
// Retrieve securely
const token = await secrets.get("NPM_TOKEN");
// Delete when done
await secrets.delete("NPM_TOKEN");
Comparison with Environment Variablesβ
| Feature | Env Vars | Bun.secrets |
|---|---|---|
| Encryption | β Plain text | β OS-encrypted |
| Memory dumps | β Exposed | β Zeroed memory |
| Process listing | β Visible in ps | β Hidden |
| Log filtering | β Manual | β Automatic |
| Update without restart | β No | β Yes |
Fallback Strategyβ
Universal Release automatically falls back to environment variables if secrets aren't available:
async get(name: string): Promise<string | null> {
try {
const secret = await Bun.secrets.get({ service, name });
if (secret) return secret;
} catch {
// Keychain unavailable or secret doesn't exist
}
// Fallback to environment
return Bun.env[name] || process.env[name] || null;
}
Security Best Practicesβ
β DOβ
// Use Bun.secrets for API tokens
await secrets.set("NPM_TOKEN", token);
// Auto-escaped interpolation
await $`npm publish ${packageName}`;
// Quiet mode for sensitive output
await $`docker login`.quiet();
β DON'Tβ
// Don't store secrets in .env for production
// .env.local is gitignored, but keychain is better
// Don't bypass auto-escaping
(await $`npm publish`) + unsafeInput; // DANGEROUS!
// Don't log secrets
console.log(process.env.NPM_TOKEN); // BAD!
CI/CD Integrationβ
GitHub Actionsβ
- uses: oven-sh/setup-bun@v2
- name: Set secrets
run: |
bun run release secrets set NPM_TOKEN <<< "${{ secrets.NPM_TOKEN }}"
- name: Publish
run: bun run release publish
Local Developmentβ
# First time setup
release secrets set NPM_TOKEN
release secrets set CARGO_TOKEN
# Future releases just work!
release publish
Migration from .envβ
One-time migration:
# Import from .env file
release secrets import < .env
β Imported 3 secrets to OS keychain
# Remove from .env (keep .env.example)
rm .env
Next Stepsβ
- Shell API - Cross-platform commands
- Semver API - Version operations