金鑰
使用 Bun 的 Secrets API 安全儲存和檢索敏感憑據
使用作業系統的本地憑據儲存 API 安全地儲存和檢索敏感憑據。
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。
安全注意事項
- 加密:作業系統的憑據管理器會加密憑據
- 訪問控制:只有儲存憑據的使用者才能檢索它
- 不以明文儲存:密碼絕不會以明文形式儲存
- 記憶體安全:Bun 會在使用後將密碼所在的記憶體清零
- 行程隔離:憑據按使用者帳戶隔離
限制
- 最大密碼長度因平臺而異(通常為 2048-4096 位元組)
- 保持
service和name足夠簡短(少於 256 個字元) - 根據平臺的不同,某些特殊字元可能需要轉義
- 需要適當的系統服務:
- Linux:必須執行 Secret Service 守護行程
- macOS:必須可用鑰匙串訪問
- Windows:必須啟用憑據管理器服務
與環境變數的對比
與環境變數不同,Bun.secrets:
- ✅ 在靜態儲存時加密憑據(由作業系統提供)
- ✅ 避免在行程記憶體轉儲中暴露金鑰(Bun 會在不再需要時將記憶體清零)
- ✅ 在應用程式重啟後仍然有效
- ✅ 無需重啟應用程式即可更新
- ✅ 提供使用者級訪問控制
- ❌ 需要作業系統憑據服務
- ❌ 不太適用於部署金鑰(生產環境中請使用環境變數)
最佳實踐
-
使用描述性服務名稱:與工具或應用程式名稱保持一致 如果你正在為外部使用建置 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; persist?: "local" | "enterprise" }): Promise<void>;
delete(options: SecretsOptions): Promise<boolean>;
}
const secrets: Secrets;
}