使用操作系统的本地凭据存储 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:钥匙串服务(Keychain Services)
- Linux:libsecret(GNOME Keyring、KWallet 等)
- Windows:Windows 凭据管理器
所有操作均为异步且非阻塞,运行在 Bun 的线程池中。
未来,我们可能会添加一个额外的 provider 选项,以便更适合生产环境中的部署秘密,但目前此 API 主要适用于本地开发工具。
API
Bun.secrets.get(options)
检索已存储的凭据。
import { secrets } from "bun";
const password = await Bun.secrets.get({
service: "my-app",
name: "[email protected]",
});
// 返回值: string | null
// 或者,如果你喜欢非对象参数
const password = await Bun.secrets.get("my-app", "[email protected]");
参数:
options.service (字符串,必填)- 服务或应用名称
options.name (字符串,必填)- 用户名或账号标识符
返回值:
Promise<string | null> - 存储的密码,找不到时返回 null
Bun.secrets.set(options, value)
存储或更新凭据。
import { secrets } from "bun";
await secrets.set({
service: "my-app",
name: "[email protected]",
value: "super-secret-password",
});
参数:
options.service (字符串,必填)- 服务或应用名称
options.name (字符串,必填)- 用户名或账号标识符
value (字符串,必填)- 要存储的密码或秘密
注意:
- 如果给定的 service/name 组合已存在凭据,将会被覆盖
- 存储的值由操作系统加密
Bun.secrets.delete(options)
删除存储的凭据。
const deleted = await Bun.secrets.delete({
service: "my-app",
name: "[email protected]",
value: "super-secret-password",
});
// 返回值: boolean
参数:
options.service (字符串,必填)- 服务或应用名称
options.name (字符串,必填)- 用户名或账号标识符
返回值:
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: "[email protected]",
value: "old-password",
});
// 更新为新密码
await Bun.secrets.set({
service: "email-server",
name: "[email protected]",
value: "new-password",
});
// 旧密码被替换
平台行为
macOS(钥匙串)
- 凭据存储在名称对应的登录钥匙串中
- 钥匙串可能首次使用时会弹出访问权限提示
- 凭据跨系统重启保存
- 仅可被存储凭据的名称访问
Linux(libsecret)
- 需要 secret service 守护进程(如 GNOME Keyring、KWallet 等)
- 凭据存储在默认集合中
- 如果钥匙环被锁,可能会提示解锁
- secret service 必须在运行中
Windows(凭据管理器)
- 凭据存储在 Windows 凭据管理器中
- 可在控制面板 → 凭据管理器 → Windows 凭据中查看
- 以
CRED_PERSIST_ENTERPRISE 标记持久保存,且为用户作用域
- 使用 Windows 数据保护 API 加密
安全考虑
- 加密:凭据由操作系统凭据管理器加密保存
- 访问控制:只有存储凭据的名称能检索该凭据
- 无明文存储:密码永远不以明文形式存储
- 内存安全:Bun 在使用后会清零密码内存
- 进程隔离:凭据按名称账户隔离
- 最大密码长度依平台不同(通常为 2048-4096 字节)
- 服务和名称长度应合理(< 256 个字符)
- 部分特殊字符根据平台可能需要转义
- 需要对应操作系统的服务:
- Linux:secret service 守护进程必须运行
- macOS:要有钥匙串访问权限
- Windows:凭据管理器服务必须启用
与环境变量的对比
与环境变量不同,Bun.secrets:
- ✅ 静态存储时加密(依赖操作系统)
- ✅ 避免凭据暴露于进程内存转储(不需要时内存被清零)
- ✅ 可跨应用重启存活
- ✅ 可在不重启应用情况下更新
- ✅ 基于名称的访问控制
- ❌ 依赖操作系统凭据服务
- ❌ 对部署秘密支持不足(生产环境建议使用环境变量)
最佳实践
-
使用描述性服务名:与工具或应用名称匹配
如果你构建对外的 CLI,建议为服务名使用 UTI(统一类型标识符)。
// 好 - 与实际工具匹配
{ service: "com.docker.hub", name: "username" }
{ service: "com.vercel.cli", name: "team-name" }
// 避免 - 太过泛泛
{ service: "api", name: "key" }
-
仅存储凭据:不要用此 API 存储应用配置
该 API 速度较慢,某些配置还是需要用配置文件。
-
适用于本地开发工具:
- ✅ 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;
}