mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
337 lines
8.4 KiB
Plaintext
337 lines
8.4 KiB
Plaintext
---
|
|
title: Secrets
|
|
description: Use Bun's Secrets API to store and retrieve sensitive credentials securely
|
|
---
|
|
|
|
Store and retrieve sensitive credentials securely using the operating system's native credential storage APIs.
|
|
|
|
<Warning>This API is new and experimental. It may change in the future.</Warning>
|
|
|
|
```typescript index.ts icon="/icons/typescript.svg"
|
|
import { secrets } from "bun";
|
|
|
|
const githubToken = await secrets.get({
|
|
service: "my-cli-tool",
|
|
name: "github-token",
|
|
});
|
|
|
|
if (!githubToken) {
|
|
const response = await fetch("https://api.github.com/name", {
|
|
headers: { Authorization: `token ${githubToken}` },
|
|
});
|
|
console.log("Please enter your GitHub token");
|
|
} else {
|
|
await secrets.set({
|
|
service: "my-cli-tool",
|
|
name: "github-token",
|
|
value: prompt("Please enter your GitHub token"),
|
|
});
|
|
console.log("GitHub token stored");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
`Bun.secrets` provides a cross-platform API for managing sensitive credentials that CLI tools and development applications typically store in plaintext files like `~/.npmrc`, `~/.aws/credentials`, or `.env` files. It uses:
|
|
|
|
- **macOS**: Keychain Services
|
|
- **Linux**: libsecret (GNOME Keyring, KWallet, etc.)
|
|
- **Windows**: Windows Credential Manager
|
|
|
|
All operations are asynchronous and non-blocking, running on Bun's threadpool.
|
|
|
|
<Note>
|
|
In the future, we may add an additional `provider` option to make this better for production deployment secrets, but
|
|
today this API is mostly useful for local development tools.
|
|
</Note>
|
|
|
|
---
|
|
|
|
## API
|
|
|
|
### `Bun.secrets.get(options)`
|
|
|
|
Retrieve a stored credential.
|
|
|
|
```typescript
|
|
import { secrets } from "bun";
|
|
|
|
const password = await Bun.secrets.get({
|
|
service: "my-app",
|
|
name: "alice@example.com",
|
|
});
|
|
// Returns: string | null
|
|
|
|
// Or if you prefer without an object
|
|
const password = await Bun.secrets.get("my-app", "alice@example.com");
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
- `options.service` (string, required) - The service or application name
|
|
- `options.name` (string, required) - The username or account identifier
|
|
|
|
**Returns:**
|
|
|
|
- `Promise<string | null>` - The stored password, or `null` if not found
|
|
|
|
### `Bun.secrets.set(options, value)`
|
|
|
|
Store or update a credential.
|
|
|
|
```typescript
|
|
import { secrets } from "bun";
|
|
|
|
await secrets.set({
|
|
service: "my-app",
|
|
name: "alice@example.com",
|
|
value: "super-secret-password",
|
|
});
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
- `options.service` (string, required) - The service or application name
|
|
- `options.name` (string, required) - The username or account identifier
|
|
- `value` (string, required) - The password or secret to store
|
|
|
|
**Notes:**
|
|
|
|
- If a credential already exists for the given service/name combination, it will be replaced
|
|
- The stored value is encrypted by the operating system
|
|
|
|
### `Bun.secrets.delete(options)`
|
|
|
|
Delete a stored credential.
|
|
|
|
```typescript
|
|
const deleted = await Bun.secrets.delete({
|
|
service: "my-app",
|
|
name: "alice@example.com",
|
|
value: "super-secret-password",
|
|
});
|
|
// Returns: boolean
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
- `options.service` (string, required) - The service or application name
|
|
- `options.name` (string, required) - The username or account identifier
|
|
|
|
**Returns:**
|
|
|
|
- `Promise<boolean>` - `true` if a credential was deleted, `false` if not found
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Storing CLI Tool Credentials
|
|
|
|
```javascript
|
|
// Store GitHub CLI token (instead of ~/.config/gh/hosts.yml)
|
|
await Bun.secrets.set({
|
|
service: "my-app.com",
|
|
name: "github-token",
|
|
value: "ghp_xxxxxxxxxxxxxxxxxxxx",
|
|
});
|
|
|
|
// Or if you prefer without an object
|
|
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");
|
|
|
|
// Store npm registry token (instead of ~/.npmrc)
|
|
await Bun.secrets.set({
|
|
service: "npm-registry",
|
|
name: "https://registry.npmjs.org",
|
|
value: "npm_xxxxxxxxxxxxxxxxxxxx",
|
|
});
|
|
|
|
// Retrieve for API calls
|
|
const token = await Bun.secrets.get({
|
|
service: "gh-cli",
|
|
name: "github.com",
|
|
});
|
|
|
|
if (token) {
|
|
const response = await fetch("https://api.github.com/name", {
|
|
headers: {
|
|
Authorization: `token ${token}`,
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
### Migrating from Plaintext Config Files
|
|
|
|
```javascript
|
|
// Instead of storing in ~/.aws/credentials
|
|
await Bun.secrets.set({
|
|
service: "aws-cli",
|
|
name: "AWS_SECRET_ACCESS_KEY",
|
|
value: process.env.AWS_SECRET_ACCESS_KEY,
|
|
});
|
|
|
|
// Instead of .env files with sensitive data
|
|
await Bun.secrets.set({
|
|
service: "my-app",
|
|
name: "api-key",
|
|
value: "sk_live_xxxxxxxxxxxxxxxxxxxx",
|
|
});
|
|
|
|
// Load at runtime
|
|
const apiKey =
|
|
(await Bun.secrets.get({
|
|
service: "my-app",
|
|
name: "api-key",
|
|
})) || process.env.API_KEY; // Fallback for CI/production
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```javascript
|
|
try {
|
|
await Bun.secrets.set({
|
|
service: "my-app",
|
|
name: "alice",
|
|
value: "password123",
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to store credential:", error.message);
|
|
}
|
|
|
|
// Check if a credential exists
|
|
const password = await Bun.secrets.get({
|
|
service: "my-app",
|
|
name: "alice",
|
|
});
|
|
|
|
if (password === null) {
|
|
console.log("No credential found");
|
|
}
|
|
```
|
|
|
|
### Updating Credentials
|
|
|
|
```javascript
|
|
// Initial password
|
|
await Bun.secrets.set({
|
|
service: "email-server",
|
|
name: "admin@example.com",
|
|
value: "old-password",
|
|
});
|
|
|
|
// Update to new password
|
|
await Bun.secrets.set({
|
|
service: "email-server",
|
|
name: "admin@example.com",
|
|
value: "new-password",
|
|
});
|
|
|
|
// The old password is replaced
|
|
```
|
|
|
|
---
|
|
|
|
## Platform Behavior
|
|
|
|
### macOS (Keychain)
|
|
|
|
- Credentials are stored in the name's login keychain
|
|
- The keychain may prompt for access permission on first use
|
|
- Credentials persist across system restarts
|
|
- Accessible by the name who stored them
|
|
|
|
### Linux (libsecret)
|
|
|
|
- Requires a secret service daemon (GNOME Keyring, KWallet, etc.)
|
|
- Credentials are stored in the default collection
|
|
- May prompt for unlock if the keyring is locked
|
|
- The secret service must be running
|
|
|
|
### Windows (Credential Manager)
|
|
|
|
- Credentials are stored in Windows Credential Manager
|
|
- Visible in Control Panel → Credential Manager → Windows Credentials
|
|
- Persist with `CRED_PERSIST_ENTERPRISE` flag so it's scoped per user
|
|
- Encrypted using Windows Data Protection API
|
|
|
|
## Security Considerations
|
|
|
|
1. **Encryption**: Credentials are encrypted by the operating system's credential manager
|
|
2. **Access Control**: Only the name who stored the credential can retrieve it
|
|
3. **No Plain Text**: Passwords are never stored in plain text
|
|
4. **Memory Safety**: Bun zeros out password memory after use
|
|
5. **Process Isolation**: Credentials are isolated per name account
|
|
|
|
## Limitations
|
|
|
|
- Maximum password length varies by platform (typically 2048-4096 bytes)
|
|
- Service and name names should be reasonable lengths (< 256 characters)
|
|
- Some special characters may need escaping depending on the platform
|
|
- Requires appropriate system services:
|
|
- Linux: Secret service daemon must be running
|
|
- macOS: Keychain Access must be available
|
|
- Windows: Credential Manager service must be enabled
|
|
|
|
---
|
|
|
|
## Comparison with Environment Variables
|
|
|
|
Unlike environment variables, `Bun.secrets`:
|
|
|
|
- ✅ Encrypts credentials at rest (thanks to the operating system)
|
|
- ✅ Avoids exposing secrets in process memory dumps (memory is zeroed after its no longer needed)
|
|
- ✅ Survives application restarts
|
|
- ✅ Can be updated without restarting the application
|
|
- ✅ Provides name-level access control
|
|
- ❌ Requires OS credential service
|
|
- ❌ Not very useful for deployment secrets (use environment variables in production)
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Use descriptive service names**: Match the tool or application name
|
|
If you're building a CLI for external use, you probably should use a UTI (Uniform Type Identifier) for the service name.
|
|
|
|
```javascript
|
|
// Good - matches the actual tool
|
|
{ service: "com.docker.hub", name: "username" }
|
|
{ service: "com.vercel.cli", name: "team-name" }
|
|
|
|
// Avoid - too generic
|
|
{ service: "api", name: "key" }
|
|
```
|
|
|
|
2. **Credentials-only**: Don't store application configuration in this API
|
|
This API is slow, you probably still need to use a config file for some things.
|
|
|
|
3. **Use for local development tools**:
|
|
- ✅ CLI tools (gh, npm, docker, kubectl)
|
|
- ✅ Local development servers
|
|
- ✅ Personal API keys for testing
|
|
- ❌ Production servers (use proper secret management)
|
|
|
|
---
|
|
|
|
## TypeScript
|
|
|
|
```typescript
|
|
namespace Bun {
|
|
interface SecretsOptions {
|
|
service: string;
|
|
name: string;
|
|
}
|
|
|
|
interface Secrets {
|
|
get(options: SecretsOptions): Promise<string | null>;
|
|
set(options: SecretsOptions, value: string): Promise<void>;
|
|
delete(options: SecretsOptions): Promise<boolean>;
|
|
}
|
|
|
|
const secrets: Secrets;
|
|
}
|
|
```
|