运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

密钥

使用 Bun 的 Secrets API 安全存储和检索敏感凭据

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

index.ts
import { secrets } from "bun";

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

if (!githubToken) {
  githubToken = prompt("请输入您的 GitHub 令牌");

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

  console.log("GitHub 令牌已存储");
}

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

console.log(`登录用户为 ${(await response.json()).login}`);

概览

Bun.secrets 提供了一个跨平台 API,用于管理敏感凭据,这些凭据通常由 CLI 工具和开发应用以明文形式存储在 ~/.npmrc、~/.aws/credentials 或 .env 等文件中。它使用:

  • macOS:钥匙串服务
  • Linux:libsecret(GNOME Keyring、KWallet 以及其他 Secret Service 守护进程)
  • Windows:Windows 凭据管理器

所有操作均为异步且非阻塞,运行在 Bun 的线程池中。


API

Bun.secrets.get(options)

检索已存储的凭据。

TypeScript
import { secrets } from "bun";

const password = await Bun.secrets.get({
  service: "my-app",
  name: "alice@example.com",
});
// 返回值: string | null

// 或者,如果你喜欢非对象参数
const password = await Bun.secrets.get("my-app", "alice@example.com");

参数:

  • options.service (字符串,必填)- 服务或应用名称
  • options.name (字符串,必填)- 用户名或账号标识符

返回值:

  • Promise<string | null> - 存储的密码,找不到时返回 null

Bun.secrets.set(options)

存储或更新凭据。

TypeScript
import { secrets } from "bun";

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

参数:

  • options.service(字符串,必填)- 服务或应用名称
  • options.name(字符串,必填)- 用户名或账号标识符
  • options.value(字符串,必填)- 要存储的密码或密钥
  • options.persist("local" 或 "enterprise",可选)- 仅限 Windows。哪些计算机可以看到此凭据。默认为 "enterprise"。请参阅 Windows(凭据管理器)

注意:

  • 如果指定的服务/名称组合已存在凭据,Bun 会替换该凭据
  • 操作系统会加密存储的值

Bun.secrets.delete(options)

删除存储的凭据。

TypeScript
const deleted = await Bun.secrets.delete({
  service: "my-app",
  name: "alice@example.com",
});
// 返回值: boolean

参数:

  • options.service (字符串,必填)- 服务或应用名称
  • options.name (字符串,必填)- 用户名或账号标识符

返回值:

  • Promise<boolean> - 如果删除成功返回 true,找不到则返回 false

示例

存储 CLI 工具凭据

JavaScript
// 存储 GitHub CLI 令牌(替代 ~/.config/gh/hosts.yml)
await Bun.secrets.set({
  service: "my-app.com",
  name: "github-token",
  value: "ghp_xxxxxxxxxxxxxxxxxxxx",
});

// 或者,如果你喜欢非对象参数
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");

// 存储 npm 仓库令牌(替代 ~/.npmrc)
await Bun.secrets.set({
  service: "npm-registry",
  name: "https://registry.npmjs.org",
  value: "npm_xxxxxxxxxxxxxxxxxxxx",
});

// API 调用时检索
const token = await Bun.secrets.get({
  service: "my-app.com",
  name: "github-token",
});

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

从明文配置文件迁移

JavaScript
// 替代存储在 ~/.aws/credentials 中
await Bun.secrets.set({
  service: "aws-cli",
  name: "AWS_SECRET_ACCESS_KEY",
  value: process.env.AWS_SECRET_ACCESS_KEY,
});

// 替代 .env 文件存储敏感数据
await Bun.secrets.set({
  service: "my-app",
  name: "api-key",
  value: "sk_live_xxxxxxxxxxxxxxxxxxxx",
});

// 运行时加载
const apiKey =
  (await Bun.secrets.get({
    service: "my-app",
    name: "api-key",
  })) || process.env.API_KEY; // CI/生产环境回退

错误处理

JavaScript
try {
  await Bun.secrets.set({
    service: "my-app",
    name: "alice",
    value: "password123",
  });
} catch (error) {
  console.error("存储凭据失败:", error.message);
}

// 检查凭据是否存在
const password = await Bun.secrets.get({
  service: "my-app",
  name: "alice",
});

if (password === null) {
  console.log("未找到凭据");
}

更新凭据

JavaScript
// 初始密码
await Bun.secrets.set({
  service: "email-server",
  name: "admin@example.com",
  value: "old-password",
});

// 更新为新密码
await Bun.secrets.set({
  service: "email-server",
  name: "admin@example.com",
  value: "new-password",
});

// 旧密码被替换

平台行为

macOS(钥匙串)

  • Bun 将凭据存储在用户的登录钥匙串中
  • 首次使用时,钥匙串可能会提示请求访问权限
  • 凭据在系统重启后仍会保留
  • 只有存储凭据的用户才能访问

Linux(libsecret)

  • 需要 GNOME Keyring 或 KWallet 等 Secret Service 守护进程
  • Bun 将凭据存储在默认集合中
  • 如果钥匙串已锁定,可能会提示解锁
  • Secret Service 必须正在运行

Windows(凭据管理器)

  • Bun 将凭据存储在 Windows 凭据管理器中
  • 可在“控制面板”→“凭据管理器”→“Windows 凭据”中查看
  • 默认使用 CRED_PERSIST_ENTERPRISE 标志持久化,因此凭据按用户范围保存
  • 使用 Windows Data Protection API 加密

使用 CRED_PERSIST_ENTERPRISE 时,具有漫游状态的用户帐户(例如域中的漫游配置文件)也能在其他计算机上看到此凭据。若要将密钥保留在当前计算机上,例如仅属于一台设备的刷新令牌,请传入 persist: "local"。Bun 随后会使用 CRED_PERSIST_LOCAL_MACHINE 存储该凭据。

TypeScript
import { secrets } from "bun";

await secrets.set({
  service: "my-app",
  name: "refresh-token",
  value: "rt_xxxxxxxxxxxxxxxxxxxx",
  persist: "local",
});

每次调用 set() 都会替换整个条目,因此以最近一次调用 set() 的 persist 设置为准。macOS 和 Linux 会忽略 persist。

安全注意事项

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

限制

  • 最大密码长度因平台而异(通常为 2048-4096 字节)
  • 保持 service 和 name 足够简短(少于 256 个字符)
  • 根据平台的不同,某些特殊字符可能需要转义
  • 需要适当的系统服务:
    • Linux:必须运行 Secret Service 守护进程
    • macOS:必须可用钥匙串访问
    • Windows:必须启用凭据管理器服务

与环境变量的对比

与环境变量不同,Bun.secrets:

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

最佳实践

  1. 使用描述性服务名称:与工具或应用程序名称保持一致 如果你正在为外部使用构建 CLI,请使用 UTI(统一类型标识符)作为服务名称。

    JavaScript
    // 好 - 与实际工具匹配
    { service: "com.docker.hub", name: "username" }
    { service: "com.vercel.cli", name: "team-name" }
    
    // 避免 - 太过泛泛
    { service: "api", name: "key" }
    
  2. 仅存储凭据:不要在此 API 中存储应用程序配置 此 API 速度较慢;请将非机密设置保存在配置文件中。

  3. 适用于本地开发工具:

    • ✅ CLI 工具(gh、npm、docker、kubectl)
    • ✅ 本地开发服务器
    • ✅ 用于测试的个人 API 密钥
    • ❌ 生产服务器(建议使用专业的机密管理方案)

TypeScript 类型定义

TypeScript
namespace Bun {
  interface SecretsOptions {
    service: string;
    name: string;
  }

  interface Secrets {
    get(options: SecretsOptions): Promise<string | null>;
    set(options: SecretsOptions & { value: string; persist?: "local" | "enterprise" }): Promise<void>;
    delete(options: SecretsOptions): Promise<boolean>;
  }

  const secrets: Secrets;
}