Skip to content

オペレーティングシステムのネイティブな認証情報ストレージ 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 は、CLI ツールや開発アプリケーションが通常 ~/.npmrc~/.aws/credentials.env ファイルなどのプレーンテキストファイルに保存する機密認証情報を管理するためのクロスプラットフォーム API を提供します。以下の機能を使用します:

  • macOS: キーチェーンサービス
  • Linux: libsecret(GNOME Keyring、KWallet など)
  • Windows: Windows 認証情報マネージャー

すべての操作は非同期でノンブロッキングであり、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(文字列、必須)- 保存するパスワードまたはシークレット

注記:

  • 指定された service/name の組み合わせの認証情報が既に存在する場合、置き換えられます
  • 保存された値はオペレーティングシステムによって暗号化されます

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(キーチェーン)

  • 認証情報はユーザーのログインキーチェーンに保存されます
  • 初回使用時にアクセス許可のプロンプトが表示される場合があります
  • 認証情報はシステム再起動後も保持されます
  • 保存したユーザーのみがアクセスできます

Linux(libsecret)

  • シークレットサービスデーモン(GNOME Keyring、KWallet など)が必要です
  • 認証情報はデフォルトのコレクションに保存されます
  • キーリングがロックされている場合、ロック解除のプロンプトが表示される場合があります
  • シークレットサービスが実行されている必要があります

Windows(認証情報マネージャー)

  • 認証情報は Windows 認証情報マネージャーに保存されます
  • コントロールパネル → 認証情報マネージャー → Windows 認証情報で表示できます
  • CRED_PERSIST_ENTERPRISE フラグで保持され、ユーザーごとにスコープされます
  • Windows データ保護 API を使用して暗号化されます

セキュリティに関する考慮事項

  1. 暗号化: 認証情報はオペレーティングシステムの認証情報マネージャーによって暗号化されます
  2. アクセス制御: 認証情報を保存したユーザーのみが取得できます
  3. プレーンテキストなし: パスワードはプレーンテキストで保存されることはありません
  4. メモリの安全性: Bun は使用後にパスワードメモリをゼロクリアします
  5. プロセスの分離: 認証情報はアカウントごとに分離されます

制限事項

  • 最大パスワード長はプラットフォームによって異なります(通常 2048-4096 バイト)
  • サービス名と名前は適切な長さ(< 256 文字)にする必要があります
  • プラットフォームによっては、一部の特殊文字のエスケープが必要な場合があります
  • 適切なシステムサービスが必要です:
    • Linux: シークレットサービスデーモンが実行されている必要があります
    • macOS: キーチェーンアクセスが利用可能である必要があります
    • Windows: 認証情報マネージャーサービスが有効になっている必要があります

環境変数との比較

環境変数とは異なり、Bun.secrets は以下の特徴があります:

  • ✅ 認証情報をディスク上で暗号化(オペレーティングシステムのおかげで)
  • ✅ プロセスメモリダンプにシークレットを公開しない(不要になった後にメモリがゼロクリアされる)
  • ✅ アプリケーションの再起動後も保持
  • ✅ アプリケーションを再起動せずに更新可能
  • ✅ ユーザーレベルのアクセス制御を提供
  • ❌ OS 認証情報サービスが必要
  • ❌ デプロイメントシークレットにはあまり役立たない(本番環境では環境変数を使用)

ベストプラクティス

  1. 説明的なサービス名を使用: ツールまたはアプリケーション名に一致させる 外部向けの CLI を構築している場合、サービス名に UTI(Uniform Type Identifier)を使用するべきです。

    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): Promise<void>;
    delete(options: SecretsOptions): Promise<boolean>;
  }

  const secrets: Secrets;
}

Bun by www.bunjs.com.cn 編集