Skip to main content
使用操作系统的原生凭证存储 API 安全地存储和检索敏感凭证。
此 API 是新的且处于实验阶段。未来可能会发生变化。
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2index.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: 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",
});
// 返回: string | null

// 或者如果您喜欢不用对象的方式
const password = await Bun.secrets.get("my-app", "alice@example.com");
参数:
  • options.service (string, 必需) - 服务或应用程序名称
  • options.name (string, 必需) - 用户名或账户标识符
返回:
  • 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, 必需) - 服务或应用程序名称
  • options.name (string, 必需) - 用户名或账户标识符
  • value (string, 必需) - 要存储的密码或密钥
注意事项:
  • 如果给定的服务/名称组合已存在凭证,它将被替换
  • 存储的值由操作系统加密

Bun.secrets.delete(options)

删除存储的凭证。
const deleted = await Bun.secrets.delete({
  service: "my-app",
  name: "alice@example.com",
  value: "super-secret-password",
});
// 返回: boolean
参数:
  • options.service (string, 必需) - 服务或应用程序名称
  • options.name (string, 必需) - 用户名或账户标识符
返回:
  • Promise<boolean> - 如果凭证被删除则返回 true,如果未找到则返回 false

示例

存储 CLI 工具凭证

// 存储 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: "gh-cli",
  name: "github.com",
});

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

从纯文本配置文件迁移

// 代替存储在 ~/.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/生产环境的后备方案

错误处理

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("未找到凭证");
}

更新凭证

// 初始密码
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 (Keychain)

  • 凭证存储在名称的登录钥匙串中
  • 钥匙串可能在首次使用时提示访问权限
  • 凭证在系统重启后仍然存在
  • 只有存储它们的名称才能访问

Linux (libsecret)

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

Windows (Credential Manager)

  • 凭证存储在 Windows Credential Manager 中
  • 在控制面板 → 凭据管理器 → Windows 凭据中可见
  • 使用 CRED_PERSIST_ENTERPRISE 标志持久化,因此按用户范围划分
  • 使用 Windows 数据保护 API 加密

安全考虑

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

限制

  • 最大密码长度因平台而异(通常为 2048-4096 字节)
  • 服务和名称应该使用合理的长度(< 256 个字符)
  • 根据平台不同,某些特殊字符可能需要转义
  • 需要适当的系统服务:
    • Linux: 密钥服务守护进程必须正在运行
    • macOS: Keychain Access 必须可用
    • Windows: Credential Manager 服务必须已启用

与环境变量的比较

与环境变量不同,Bun.secrets:
  • ✅ 在静止状态下加密凭证(感谢操作系统)
  • ✅ 避免在进程内存转储中暴露密钥(内存在不再需要后被清零)
  • ✅ 在应用程序重启后仍然存在
  • ✅ 可以在不重新启动应用程序的情况下更新
  • ✅ 提供名称级别的访问控制
  • ❌ 需要操作系统凭证服务
  • ❌ 对于部署密钥不是很实用(在生产环境中使用环境变量)

最佳实践

  1. 使用描述性的服务名称: 匹配工具或应用程序名称 如果您正在为外部使用构建 CLI,您可能应该对服务名称使用 UTI(统一类型标识符)。
    // 好 - 匹配实际工具
    { 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

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