使用操作系统的原生凭证存储 API 安全地存储和检索敏感凭证。
typescript
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 的线程池上运行。
NOTE
将来,我们可能会添加一个额外的 `provider` 选项,使其更适合生产部署凭证,但 目前此 API 主要用于本地开发工具。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, value)
存储或更新凭证。
typescript
import { secrets } from "bun";
await secrets.set({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});参数:
options.service(字符串,必需)- 服务或应用程序名称options.name(字符串,必需)- 用户名或账户标识符value(字符串,必需)- 要存储的密码或凭证
说明:
- 如果已存在给定服务/名称组合的凭证,它将被替换
- 存储的值由操作系统加密
Bun.secrets.delete(options)
删除存储的凭证。
typescript
const deleted = await Bun.secrets.delete({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});
// 返回: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: "gh-cli",
name: "github.com",
});
if (token) {
const response = await fetch("https://api.github.com/name", {
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 (Keychain)
- 凭证存储在用户的登录钥匙串中
- 首次使用时,钥匙串可能会提示访问权限
- 凭证在系统重启后持久存在
- 仅存储凭证的用户可访问
Linux (libsecret)
- 需要密钥服务守护进程(GNOME Keyring、KWallet 等)
- 凭证存储在默认集合中
- 如果密钥环已锁定,可能会提示解锁
- 密钥服务必须正在运行
Windows (Credential Manager)
- 凭证存储在 Windows 凭证管理器中
- 可在控制面板 → 凭证管理器 → Windows 凭证中查看
- 使用
CRED_PERSIST_ENTERPRISE标志持久化,因此按用户范围限定 - 使用 Windows 数据保护 API 加密
安全注意事项
- 加密:凭证由操作系统的凭证管理器加密
- 访问控制:只有存储凭证的用户才能检索它
- 无明文:密码从不以明文存储
- 内存安全:Bun 在使用后清零密码内存
- 进程隔离:凭证按用户账户隔离
限制
- 最大密码长度因平台而异(通常为 2048-4096 字节)
- 服务和名称长度应合理(< 256 个字符)
- 某些特殊字符可能需要转义,具体取决于平台
- 需要适当的系统服务:
- Linux:密钥服务守护进程必须正在运行
- macOS:必须可用钥匙串访问
- Windows:必须启用凭证管理器服务
与环境变量的比较
与环境变量不同,Bun.secrets:
- ✅ 静态加密凭证(感谢操作系统)
- ✅ 避免在进程内存转储中暴露凭证(内存不再需要时被清零)
- ✅ 在应用程序重启后存活
- ✅ 可更新而无需重启应用程序
- ✅ 提供用户级访问控制
- ❌ 需要操作系统凭证服务
- ❌ 对部署凭证不太有用(生产环境使用环境变量)
最佳实践
使用描述性的服务名称:匹配工具或应用程序名称 如果你正在构建供外部使用的 CLI,你可能应该使用 UTI(统一类型标识符)作为服务名称。
javascript// 好 - 匹配实际工具 { 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
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;
}