> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets

> 使用 Bun 的 Secrets API 安全地存储和检索敏感凭据

使用操作系统的原生凭据存储 API 安全地存储和检索敏感凭据。

<Warning>此 API 是新的且实验性的。将来可能会发生变化。</Warning>

```typescript index.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { secrets } from "bun";

let githubToken: string | null = await secrets.get({
  service: "my-cli-tool",
  name: "github-token",
});

if (!githubToken) {
  githubToken = prompt("Please enter your GitHub token");

  await secrets.set({
    service: "my-cli-tool",
    name: "github-token",
    value: githubToken,
  });

  console.log("GitHub token stored");
}

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

console.log(`Logged in as ${(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>
  此 API 主要对本地开发工具有用。我们稍后可能会为生产部署密钥添加 `provider` 选项。
</Note>

***

## API

### `Bun.secrets.get(options)`

检索存储的凭据。

```typescript theme={null}
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 theme={null}
import { secrets } from "bun";

await secrets.set({
  service: "my-app",
  name: "alice@example.com",
  value: "super-secret-password",
});
```

**参数：**

* `options.service`（字符串，必需）— 服务或应用程序名称
* `options.name`（字符串，必需）— 用户名或帐户标识符
* `options.value`（字符串，必需）— 要存储的密码或密钥

**注意：**

* 如果给定服务/名称组合的凭据已存在，它会被替换
* 存储的值由操作系统加密

### `Bun.secrets.delete(options)`

删除存储的凭据。

```typescript theme={null}
const deleted = await Bun.secrets.delete({
  service: "my-app",
  name: "alice@example.com",
});
// 返回：boolean
```

**参数：**

* `options.service`（字符串，必需）— 服务或应用程序名称
* `options.name`（字符串，必需）— 用户名或帐户标识符

**返回：**

* `Promise<boolean>` — 如果成功删除凭据则返回 `true`，如果未找到则返回 `false`

***

## 示例

### 存储 CLI 工具凭据

```javascript theme={null}
// 存储 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 theme={null}
// 而不是存储在 ~/.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 theme={null}
try {
  await Bun.secrets.set({
    service: "my-app",
    name: "alice",
    value: "password123",
  });
} catch (error) {
  console.error("Failed to store credential:", error.message);
}

// 检查凭据是否存在
const password = await Bun.secrets.get({
  service: "my-app",
  name: "alice",
});

if (password === null) {
  console.log("No credential found");
}
```

### 更新凭据

```javascript theme={null}
// 初始密码
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 Data Protection API 加密

## 安全考虑

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

## 限制

* 最大密码长度因平台而异（通常为 2048-4096 字节）
* 保持 `service` 和 `name` 合理简短（少于 256 个字符）
* 某些特殊字符可能需要在平台上进行转义
* 需要适当的系统服务：
  * Linux：秘密服务守护进程必须正在运行
  * macOS：Keychain Access 必须可用
  * Windows：Credential Manager 服务必须已启用

***

## 与环境变量的比较

与环境变量不同，`Bun.secrets`：

* ✅ 对静态凭据进行加密（得益于操作系统）
* ✅ 避免在进程内存转储中暴露密钥（不再需要时内存被清零）
* ✅ 在应用程序重启后持续存在
* ✅ 可以在不重新启动应用程序的情况下更新
* ✅ 提供用户级别的访问控制
* ❌ 需要 OS 凭据服务
* ❌ 对部署密钥不太有用（生产环境使用环境变量）

***

## 最佳实践

1. **使用描述性的服务名称**：与工具或应用程序名称匹配
   如果您正在构建用于外部使用的 CLI，请为服务名称使用 UTI（统一类型标识符）。

   ```javascript theme={null}
   // 好 - 与实际工具匹配
   { 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 theme={null}
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;
}
```
