Almacena y recupera credenciales sensibles de forma segura usando las APIs nativas de almacenamiento de credenciales del sistema operativo.
import { secrets } from "bun";
let githubToken: string | null = await secrets.get({
service: "my-cli-tool",
name: "github-token",
});
if (!githubToken) {
githubToken = prompt("Por favor ingresa tu token de GitHub");
await secrets.set({
service: "my-cli-tool",
name: "github-token",
value: githubToken,
});
console.log("Token de GitHub almacenado");
}
const response = await fetch("https://api.github.com/user", {
headers: { Authorization: `token ${githubToken}` },
});
console.log(`Conectado como ${(await response.json()).login}`);Visión General
Bun.secrets proporciona una API multiplataforma para gestionar credenciales sensibles que las herramientas CLI y aplicaciones de desarrollo típicamente almacenan en archivos de texto plano como ~/.npmrc, ~/.aws/credentials, o archivos .env. Usa:
- macOS: Servicios de Keychain
- Linux: libsecret (GNOME Keyring, KWallet, etc.)
- Windows: Windows Credential Manager
Todas las operaciones son asíncronas y no bloqueantes, ejecutándose en el threadpool de Bun.
NOTE
En el futuro, podríamos agregar una opción `provider` adicional para hacer esto más útil para secretos de implementación en producción, pero hoy esta API es principalmente útil para herramientas de desarrollo local.API
Bun.secrets.get(options)
Recupera una credencial almacenada.
import { secrets } from "bun";
const password = await Bun.secrets.get({
service: "my-app",
name: "alice@example.com",
});
// Devuelve: string | null
// O si prefieres sin un objeto
const password = await Bun.secrets.get("my-app", "alice@example.com");Parámetros:
options.service(string, requerido) - El servicio o nombre de aplicaciónoptions.name(string, requerido) - El nombre de usuario o identificador de cuenta
Devuelve:
Promise<string | null>- La contraseña almacenada, onullsi no se encuentra
Bun.secrets.set(options, value)
Almacena o actualiza una credencial.
import { secrets } from "bun";
await secrets.set({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});Parámetros:
options.service(string, requerido) - El servicio o nombre de aplicaciónoptions.name(string, requerido) - El nombre de usuario o identificador de cuentavalue(string, requerido) - La contraseña o secreto a almacenar
Notas:
- Si ya existe una credencial para la combinación service/name dada, será reemplazada
- El valor almacenado está encriptado por el sistema operativo
Bun.secrets.delete(options)
Elimina una credencial almacenada.
const deleted = await Bun.secrets.delete({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});
// Devuelve: booleanParámetros:
options.service(string, requerido) - El servicio o nombre de aplicaciónoptions.name(string, requerido) - El nombre de usuario o identificador de cuenta
Devuelve:
Promise<boolean>-truesi se eliminó una credencial,falsesi no se encontró
Ejemplos
Almacenar Credenciales de Herramientas CLI
// Almacenar token de GitHub CLI (en lugar de ~/.config/gh/hosts.yml)
await Bun.secrets.set({
service: "my-app.com",
name: "github-token",
value: "ghp_xxxxxxxxxxxxxxxxxxxx",
});
// O si prefieres sin un objeto
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");
// Almacenar token del registro npm (en lugar de ~/.npmrc)
await Bun.secrets.set({
service: "npm-registry",
name: "https://registry.npmjs.org",
value: "npm_xxxxxxxxxxxxxxxxxxxx",
});
// Recuperar para llamadas a 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}`,
},
});
}Migrar desde Archivos de Configuración de Texto Plano
// En lugar de almacenar en ~/.aws/credentials
await Bun.secrets.set({
service: "aws-cli",
name: "AWS_SECRET_ACCESS_KEY",
value: process.env.AWS_SECRET_ACCESS_KEY,
});
// En lugar de archivos .env con datos sensibles
await Bun.secrets.set({
service: "my-app",
name: "api-key",
value: "sk_live_xxxxxxxxxxxxxxxxxxxx",
});
// Cargar en tiempo de ejecución
const apiKey =
(await Bun.secrets.get({
service: "my-app",
name: "api-key",
})) || process.env.API_KEY; // Fallback para CI/producciónManejo de Errores
try {
await Bun.secrets.set({
service: "my-app",
name: "alice",
value: "password123",
});
} catch (error) {
console.error("Error al almacenar credencial:", error.message);
}
// Verificar si existe una credencial
const password = await Bun.secrets.get({
service: "my-app",
name: "alice",
});
if (password === null) {
console.log("No se encontró credencial");
}Actualizar Credenciales
// Contraseña inicial
await Bun.secrets.set({
service: "email-server",
name: "admin@example.com",
value: "old-password",
});
// Actualizar a nueva contraseña
await Bun.secrets.set({
service: "email-server",
name: "admin@example.com",
value: "new-password",
});
// La contraseña anterior es reemplazadaComportamiento por Plataforma
macOS (Keychain)
- Las credenciales se almacenan en el keychain de inicio de sesión del nombre
- El keychain puede solicitar permiso de acceso en el primer uso
- Las credenciales persisten a través de reinicios del sistema
- Accesibles por el nombre que las almacenó
Linux (libsecret)
- Requiere un daemon de servicio de secretos (GNOME Keyring, KWallet, etc.)
- Las credenciales se almacenan en la colección predeterminada
- Puede solicitar desbloqueo si el keyring está bloqueado
- El servicio de secretos debe estar en ejecución
Windows (Credential Manager)
- Las credenciales se almacenan en Windows Credential Manager
- Visibles en Panel de Control → Administrador de Credenciales → Credenciales de Windows
- Persisten con la bandera
CRED_PERSIST_ENTERPRISEasí que está limitado por usuario - Encriptadas usando Windows Data Protection API
Consideraciones de Seguridad
- Encriptación: Las credenciales están encriptadas por el administrador de credenciales del sistema operativo
- Control de Acceso: Solo el nombre que almacenó la credencial puede recuperarla
- Sin Texto Plano: Las contraseñas nunca se almacenan en texto plano
- Seguridad de Memoria: Bun borra la memoria de contraseña después del uso
- Aislamiento de Proceso: Las credenciales están aisladas por nombre de cuenta
Limitaciones
- La longitud máxima de contraseña varía por plataforma (típicamente 2048-4096 bytes)
- Los nombres de service y name deben tener longitudes razonables (< 256 caracteres)
- Algunos caracteres especiales pueden necesitar escape dependiendo de la plataforma
- Requiere servicios del sistema apropiados:
- Linux: El daemon de servicio de secretos debe estar en ejecución
- macOS: Keychain Access debe estar disponible
- Windows: El servicio Credential Manager debe estar habilitado
Comparación con Variables de Entorno
A diferencia de las variables de entorno, Bun.secrets:
- ✅ Encripta credenciales en reposo (gracias al sistema operativo)
- ✅ Evita exponer secretos en volcados de memoria de proceso (la memoria se borra después de que ya no es necesaria)
- ✅ Sobrevive a reinicios de aplicación
- ✅ Se puede actualizar sin reiniciar la aplicación
- ✅ Proporciona control de acceso a nivel de nombre
- ❌ Requiere servicio de credenciales del SO
- ❌ No es muy útil para secretos de implementación (usa variables de entorno en producción)
Mejores Prácticas
Usa nombres de servicio descriptivos: Coincide con el nombre de la herramienta o aplicación Si estás construyendo un CLI para uso externo, probablemente deberías usar un UTI (Uniform Type Identifier) para el nombre de servicio.
javascript// Bueno - coincide con la herramienta real { service: "com.docker.hub", name: "username" } { service: "com.vercel.cli", name: "team-name" } // Evitar - demasiado genérico { service: "api", name: "key" }Solo credenciales: No almacenes configuración de aplicación en esta API Esta API es lenta, probablemente aún necesites usar un archivo de configuración para algunas cosas.
Usa para herramientas de desarrollo local:
- ✅ Herramientas CLI (gh, npm, docker, kubectl)
- ✅ Servidores de desarrollo local
- ✅ Claves de API personales para testing
- ❌ Servidores de producción (usa gestión de secretos apropiada)
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;
}