Skip to main content

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​

FeatureEnv VarsBun.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​