rembrembdocs

Store and retrieve sensitive credentials securely using the operating system’s native credential storage APIs.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts

import { secrets } from "bun";

let githubToken: string | null = await secrets.get({
  service: "my-cli-tool",
  name: "github-token",
});

if (!githubToken) {
  githubToken = prompt("Please enter your GitHub token");

  await secrets.set({
    service: "my-cli-tool",
    name: "github-token",
    value: githubToken,
  });

  console.log("GitHub token stored");
}

const response = await fetch("https://api.github.com/user", {
  headers: { Authorization: `token ${githubToken}` },
});

console.log(`Logged in as ${(await response.json()).login}`);

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:

All operations are asynchronous and non-blocking, running on Bun’s threadpool.


API

Bun.secrets.get(options)

Retrieve a stored credential.

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:

Returns:

Bun.secrets.set(options, value)

Store or update a credential.

import { secrets } from "bun";

await secrets.set({
  service: "my-app",
  name: "alice@example.com",
  value: "super-secret-password",
});

Parameters:

Notes:

Bun.secrets.delete(options)

Delete a stored credential.

const deleted = await Bun.secrets.delete({
  service: "my-app",
  name: "alice@example.com",
  value: "super-secret-password",
});
// Returns: boolean

Parameters:

Returns:


Examples

Storing CLI Tool Credentials

// 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

// 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

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

// 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)

Linux (libsecret)

Windows (Credential Manager)

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


Comparison with Environment Variables

Unlike environment variables, Bun.secrets:


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.

    // 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

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;
}