Безопасное хранение и получение конфиденциальных учетных данных с использованием собственных 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: Keychain Services
- Linux: libsecret (GNOME Keyring, KWallet и т.д.)
- Windows: Windows Credential Manager
Все операции асинхронны и не блокируют выполнение, работая в пуле потоков Bun.
NOTE
В будущем мы можем добавить дополнительную опцию `provider` для улучшения работы с секретами развертывания в продакшене, но сегодня этот API в основном полезен для локальных инструментов разработки.API
Bun.secrets.get(options)
Получение сохраненных учетных данных.
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(string, обязательно) - Имя сервиса или приложенияoptions.name(string, обязательно) - Имя пользователя или идентификатор учетной записи
Возвращает:
Promise<string | null>- Сохраненный пароль илиnull, если не найден
Bun.secrets.set(options, value)
Сохранение или обновление учетных данных.
import { secrets } from "bun";
await secrets.set({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});Параметры:
options.service(string, обязательно) - Имя сервиса или приложенияoptions.name(string, обязательно) - Имя пользователя или идентификатор учетной записиvalue(string, обязательно) - Пароль или секрет для хранения
Примечания:
- Если учетные данные уже существуют для данной комбинации service/name, они будут заменены
- Сохраненное значение шифруется операционной системой
Bun.secrets.delete(options)
Удаление сохраненных учетных данных.
const deleted = await Bun.secrets.delete({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});
// Возвращает: booleanПараметры:
options.service(string, обязательно) - Имя сервиса или приложенияoptions.name(string, обязательно) - Имя пользователя или идентификатор учетной записи
Возвращает:
Promise<boolean>-true, если учетные данные были удалены,false, если не найдены
Примеры
Хранение учетных данных CLI-инструментов
// Сохранение токена 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}`,
},
});
}Миграция с файлов конфигурации в виде простого текста
// Вместо хранения в ~/.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/продакшенаОбработка ошибок
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("Учетные данные не найдены");
}Обновление учетных данных
// Начальный пароль
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
- Видны в Панель управления → Credential Manager → Windows Credentials
- Сохраняются с флагом
CRED_PERSIST_ENTERPRISE, поэтому ограничены пользователем - Шифруются с использованием Windows Data Protection API
Соображения безопасности
- Шифрование: Учетные данные шифруются менеджером учетных данных операционной системы
- Контроль доступа: Только пользователь, сохранивший учетные данные, может получить их
- Без простого текста: Пароли никогда не хранятся в виде простого текста
- Безопасность памяти: Bun очищает память пароля после использования
- Изоляция процесса: Учетные данные изолированы по учетной записи пользователя
Ограничения
- Максимальная длина пароля варьируется в зависимости от платформы (обычно 2048-4096 байт)
- Имена service и name должны быть разумной длины (< 256 символов)
- Некоторые специальные символы могут требовать экранирования в зависимости от платформы
- Требуются соответствующие системные службы:
- Linux: Демон службы секретов должен быть запущен
- macOS: Keychain Access должен быть доступен
- Windows: Служба Credential Manager должна быть включена
Сравнение с переменными окружения
В отличие от переменных окружения, Bun.secrets:
- ✅ Шифрует учетные данные на диске (благодаря операционной системе)
- ✅ Избегает раскрытия секретов в дампах памяти процесса (память очищается, когда она больше не нужна)
- ✅ Сохраняется после перезапуска приложения
- ✅ Может быть обновлен без перезапуска приложения
- ✅ Предоставляет контроль доступа на уровне пользователя
- ❌ Требует службы учетных данных ОС
- ❌ Не очень полезен для секретов развертывания (используйте переменные окружения в продакшене)
Лучшие практики
Используйте описательные имена сервисов: Соответствуйте имени инструмента или приложения Если вы создаете CLI для внешнего использования, вероятно, следует использовать UTI (Uniform Type Identifier) для имени сервиса.
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
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;
}