Bun

Secrets

使用操作系统原生的凭据存储 API 安全地存储和检索敏感凭据。

实验性: 此 API 是新的且处于实验阶段。未来可能会发生更改。

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

概述

Bun.secrets 提供了一个跨平台 API,用于管理 CLI 工具和开发应用程序通常以纯文本文件形式(如 ~/.npmrc~/.aws/credentials.env 文件)存储的敏感凭据。它使用

  • macOS:Keychain Services
  • Linux:libsecret (GNOME Keyring, KWallet 等)
  • Windows:Windows Credential Manager

所有操作都是异步非阻塞的,在 Bun 的线程池上运行。

注意:将来,我们可能会添加一个额外的 provider 选项,使其更适合生产环境部署的密钥,但今天此 API 主要对本地开发工具有用。

API

Bun.secrets.get(options)

检索已存储的凭据。

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");

参数

  • options.service (string, required) - 服务或应用程序名称
  • options.name (string, required) - 用户名或账户标识符

返回

  • Promise<string | null> - 存储的密码,如果未找到则为 null

Bun.secrets.set(options, value)

存储或更新凭据。

import { secrets } from "bun";

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

参数

  • options.service (string, required) - 服务或应用程序名称
  • options.name (string, required) - 用户名或账户标识符
  • value (string, required) - 要存储的密码或密钥

备注

  • 如果给定服务/用户名组合的凭据已存在,它将被替换。
  • 存储的值由操作系统加密。

Bun.secrets.delete(options)

删除已存储的凭据。

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

参数

  • options.service (string, required) - 服务或应用程序名称
  • options.name (string, required) - 用户名或账户标识符

返回

  • Promise<boolean> - 如果删除了凭据,则为 true;如果未找到,则为 false

示例

存储 CLI 工具凭据

// 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}`,
    },
  });
}

从纯文本配置文件迁移

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

错误处理

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

更新凭据

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

平台行为

macOS (钥匙串)

  • 凭据存储在用户登录的钥匙串中。
  • 首次使用时,钥匙串可能会提示访问权限。
  • 凭据在系统重启后仍然存在。
  • 由存储凭据的用户访问。

Linux (libsecret)

  • 需要一个密钥服务守护进程(GNOME Keyring, KWallet 等)。
  • 凭据存储在默认集合中。
  • 如果密钥环被锁定,可能会提示解锁。
  • 密钥服务必须正在运行。

Windows (凭据管理器)

  • 凭据存储在 Windows 凭据管理器中。
  • 可在“控制面板”→“凭据管理器”→“Windows 凭据”中找到。
  • 使用 CRED_PERSIST_ENTERPRISE 标志持久化,以便按用户范围划分。
  • 使用 Windows 数据保护 API 加密。

安全注意事项

  1. 加密:凭据由操作系统的凭据管理器加密。
  2. 访问控制:只有存储凭据的用户才能检索它。
  3. 无纯文本:密码绝不会以纯文本形式存储。
  4. 内存安全:Bun 在使用后会清零密码内存。
  5. 进程隔离:凭据按用户账户隔离。

限制

  • 最大密码长度因平台而异(通常为 2048-4096 字节)。
  • 服务和用户名长度应合理(< 256 个字符)。
  • 某些特殊字符可能需要根据平台进行转义。
  • 需要相应的系统服务。
    • Linux:密钥服务守护进程必须正在运行。
    • macOS:钥匙串访问必须可用。
    • Windows:凭据管理器服务必须已启用。

与环境变量的比较

与环境变量不同,Bun.secrets

  • ✅ 在静止时加密凭据(得益于操作系统)
  • ✅ 避免在进程内存转储中暴露密钥(不再需要后内存会被清零)
  • ✅ 应用程序重启后仍然有效
  • ✅ 无需重启应用程序即可更新
  • ✅ 提供用户名级别的访问控制
  • ❌ 需要操作系统凭据服务
  • ❌ 对于部署密钥不太有用(生产环境请使用环境变量)

最佳实践

  1. 使用描述性的服务名称:与工具或应用程序名称匹配。 如果您正在为外部使用构建 CLI,则可能应该为服务名称使用 UTI(统一类型标识符)。

    // 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. 仅用于凭据:不要在此 API 中存储应用程序配置。 此 API 速度较慢,您可能仍然需要使用配置文件来处理某些事务。

  3. 用于本地开发工具:

    • ✅ CLI 工具(gh, npm, docker, kubectl)
    • ✅ 本地开发服务器
    • ✅ 测试用的个人 API 密钥
    • ❌ 生产服务器(使用正确的密钥管理)

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

另请参阅