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: 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 (문자열, 필수) - 저장할 비밀번호 또는 비밀

참고:

  • 주어진 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 (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 바이트)
  • 서비스 및 이름 길이는 합리적인 길이 (< 256 자) 여야 합니다
  • 플랫폼에 따라 일부 특수 문자는 이스케이프가 필요할 수 있습니다
  • 적절한 시스템 서비스가 필요합니다:
    • Linux: 비밀 서비스 데몬이 실행 중이어야 함
    • macOS: Keychain Access 를 사용할 수 있어야 함
    • Windows: Credential Manager 서비스가 활성화되어 있어야 함

환경 변수와의 비교

환경 변수와 달리 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 편집