執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

金鑰

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