Secretos: valores cifrados con wrangler secret
Los secretos de un Worker: `wrangler secret put`, valores cifrados que no aparecen ni en el código ni en la configuración versionada, cómo se leen igual que una `var` desde `env`, y el almacenamiento de solo escritura que los protege.
Una var sirve para lo que quieres que se vea; un secreto, para lo que no debe verse jamás. La clave de una pasarela de pago, el token de un servicio, la contraseña de una base de datos: valores que, si se filtran, obligan a rotarlos y a asumir que alguien los tuvo. Cloudflare los guarda cifrados y fuera de tu código, y te los entrega en el mismo env de siempre. Lo notable es que, desde dentro del Worker, un secreto y una var son indistinguibles: toda la diferencia está en dónde vive el valor y qué puedes hacer con él una vez guardado.
- Subir un secreto cifrado con
wrangler secret puty gestionarlo conlistydelete. - Entender que el secreto no vive en el código ni en el manifiesto versionado.
- Leerlo en el Worker por
env, con la misma superficie que unavar. - Manejar secretos en desarrollo local y en integración continua sin exponerlos.
Un secreto no vive en el código
El comando central es wrangler secret put NOMBRE. Al ejecutarlo, Wrangler te pide el valor por una entrada oculta, lo cifra y lo almacena asociado a ese Worker en la infraestructura de Cloudflare. El valor no se guarda de forma persistente en tu disco, no entra en wrangler.jsonc y, por tanto, no se versiona ni viaja en el repositorio.
# Sube un secreto: wrangler pide el valor por entrada oculta y lo cifra
wrangler secret put STRIPE_KEY
# Sin interaccion (util en CI): el valor entra por stdin
echo "$STRIPE_KEY" | wrangler secret put STRIPE_KEY
# Lista los secretos existentes: solo nombres, nunca valores
wrangler secret list
# Elimina uno
wrangler secret delete STRIPE_KEY
Fíjate en wrangler secret list: devuelve los nombres de los secretos y su tipo, pero no sus valores. No es una limitación de la herramienta, sino una propiedad deliberada del almacén: un secreto es de solo escritura. Una vez guardado, no existe ningún comando ni ninguna pantalla que te devuelva su texto en claro. Solo puedes sobreescribirlo o borrarlo. Si lo pierdes, no lo recuperas: lo rotas.
Subir un secreto es idempotente en el sentido práctico: volver a ejecutar wrangler secret put con el mismo nombre sobrescribe el valor anterior sin dejar rastro del viejo. No existe una operación de editar un secreto; existe reemplazarlo. Y su alcance es preciso: un secreto pertenece a un Worker concreto —y, como verás en la lección de entornos, a un entorno concreto—, no a tu cuenta entera. Dos Workers con un secreto del mismo nombre tienen dos valores independientes que no se enteran el uno del otro.
Hay también límites que conviene conocer de antemano: un secreto es texto, con un tamaño máximo por valor pensado para claves y tokens, no para archivos. Si necesitas custodiar algo voluminoso —un certificado largo, un JSON de credenciales de servicio—, lo habitual es guardar el material en un almacén apropiado y dejar en el secreto solo la clave mínima que da acceso a él.
La misma idea de mínimo se aplica al número de secretos: cada uno que añades es un nombre más que recordar, rotar y auditar. No multipliques secretos por comodidad; si dos valores siempre van juntos y pertenecen al mismo proveedor, a veces es más limpio un único secreto bien estructurado que tres sueltos que hay que mantener en sincronía.
La superficie es idéntica, el almacén no
Este es el punto que más cuesta interiorizar y el que más limpio deja el diseño. Dentro del Worker, leer un secreto es exactamente igual que leer una var:
export default {
async fetch(request, env) {
// identico a leer una var: el codigo no sabe que esta cifrada
const token = env.STRIPE_KEY;
return fetch("https://api.stripe.com/v1/charges", {
method: "POST",
headers: { authorization: `Bearer ${token}` },
});
},
} satisfies ExportedHandler<Env>;
El código no distingue —ni debe distinguir— si env.STRIPE_KEY provino de un vars en texto plano o de un secreto cifrado. Ambos son propiedades del mismo objeto env. Esa simetría en la lectura es intencionada: tu lógica de negocio no debería acoplarse a la mecánica de almacenamiento de una credencial. Toda la diferencia ocurre antes de llegar al código, en tres ejes: dónde se guarda el valor (un almacén cifrado, no el manifiesto), cómo se guarda (cifrado en reposo, inyectado en runtime, nunca horneado en el bundle) y qué puedes hacer con él después (solo escribir, nunca leer).
Vale la pena nombrar la excepción aparente: los secretos del Secrets Store, que verás en la lección siguiente, se leen con await env.X.get() en vez de como una cadena directa. No rompe la idea —el valor sigue sin poder recuperarse en claro desde fuera del runtime—, pero recuerda que la superficie idéntica vale para los secretos por Worker; el almacén centralizado añade una forma de acceso propia.
Como vars y secretos comparten el espacio de nombres de env, no pueden coexistir dos con el mismo nombre sin que uno ensombrezca al otro. En la práctica, define un nombre en un solo sitio: o es una var pública en el manifiesto, o es un secreto en el almacén cifrado. Duplicar el nombre en ambos lugares es una fuente segura de confusión sobre qué valor gana en producción.
Esa portabilidad tiene una consecuencia de diseño que conviene explotar: puedes empezar un valor como var y promoverlo a secreto —o al revés— sin tocar una línea del código que lo consume, porque env.NOMBRE se lee igual en ambos casos. La decisión de si algo es sensible puede evolucionar con el proyecto, y el desacoplamiento entre lectura y almacenamiento hace que corregirla sea una operación de configuración, no una refactorización.
Esa indiferencia del código ante el origen del valor es también una invitación a la disciplina: como env.X no delata si su valor es público o secreto, la única forma de saberlo es la convención y la revisión. Documenta en tu equipo qué nombres son secretos, para que nadie imprima en un log algo que creía inocuo.
Secretos en local y en CI
En desarrollo, wrangler dev no habla con el almacén de producción. Los secretos locales viven en un archivo .dev.vars en la raíz del proyecto, con formato dotenv, que debe estar en .gitignore desde el primer minuto:
# .dev.vars (en .gitignore, NUNCA se versiona)
STRIPE_KEY=sk_test_solo_para_local
DB_PASSWORD=postgres-de-desarrollo
Así, en local usas claves de prueba y en producción las de verdad, sin que las segundas toquen jamás tu máquina. Para poblar muchos secretos de golpe —típico al aprovisionar un entorno— existe wrangler secret bulk, que lee un archivo JSON de pares nombre-valor y los sube todos.
En integración continua no hay nadie que teclee el valor en el prompt. El patrón correcto es guardar el material sensible en el almacén de secretos de tu plataforma de CI y pasárselo a Wrangler por stdin o por variable de entorno en el momento del despliegue, autenticándote con un CLOUDFLARE_API_TOKEN de alcance mínimo. El secreto pasa del almacén del CI al almacén de Cloudflare sin quedar escrito en ningún archivo del repositorio.
Una nota de higiene que nunca sobra: el .dev.vars debe estar en .gitignore antes de escribir el primer secreto dentro, no después. El accidente clásico es crear el archivo, llenarlo de claves de prueba, trabajar una semana y descubrir en la primera revisión que se coló en un commit. Añade el patrón al .gitignore en el mismo momento en que creas el proyecto, junto a node_modules y .wrangler.
Un secreto tecleado en la terminal puede quedar guardado en el historial del shell, un archivo en claro que sobrevive a la sesión. Por eso wrangler secret put pide el valor por una entrada oculta en vez de tomarlo como argumento visible. En CI, prefiere pasar el valor por stdin desde el almacén de la plataforma antes que interpolarlo en una línea de comando que quedará impresa en los registros del pipeline.
flowchart LR DEV[desarrollador o CI] --> PUT[wrangler secret put] PUT --> STORE[almacen cifrado de cloudflare] STORE --> RT[runtime inyecta en env] RT --> CODE[el worker lee env secreto] GIT[repositorio git] -.el secreto nunca entra aqui.-> STORE style STORE fill:#f38ba8,color:#11111b style CODE fill:#a6e3a1,color:#11111b style GIT fill:#f9e2af,color:#11111b
Rotar es la operación normal, no la excepción
La propiedad de solo escritura empuja hacia una mentalidad concreta: un secreto no es un valor que pones una vez y olvidas, sino uno que rotas con regularidad. Como no puedes leerlo de vuelta, la duda de si sigue siendo válido el que pusiste solo se responde reemplazándolo. Los equipos maduros rotan credenciales de forma periódica y, de forma obligatoria, ante cualquier sospecha de fuga; wrangler secret put sobre el nombre existente y wrangler secret delete para el que ya no se usa son las dos operaciones que sostienen ese ciclo de vida.
Y para saber qué merece este tratamiento, aquí está el catálogo de lo que casi siempre es un secreto:
Claves de API
Tokens de servicios de terceros: pagos, correo, mapas, IA. Autorizan gasto o acceso en tu nombre; una fuga cuesta dinero o datos.
Credenciales de base de datos
Cadenas de conexión con usuario y contraseña, o el secreto de Hyperdrive. Abren la puerta a los datos de tus usuarios.
Secretos de firma
Claves con las que firmas o verificas tokens de sesión y JWT. Quien las tenga puede falsificar identidades en tu sistema.
OAuth y webhooks
El client secret de un proveedor y los secretos con que validas webhooks entrantes. Autentican una relación entre servicios.
Un principio afina la lista: cada secreto debería tener el menor alcance posible. Un token que solo necesita leer no debería poder escribir; una clave que sirve a un entorno no debería valer en otro. Cuanto más acotado es el poder de una credencial, menos daño hace su fuga y más tranquila es su rotación. El secreto ideal es aquel que, si se filtrara, te haría encogerte de hombros.
Ese ideal rara vez se alcanza del todo, pero orienta cada decisión: al pedir un token a un proveedor, elige el conjunto de permisos más pequeño que cumpla la tarea; al crear un usuario de base de datos para el Worker, dale acceso solo a las tablas que toca. La rotación y el mínimo privilegio son las dos caras de una misma moneda: cuanto menos puede hacer una credencial y con más frecuencia la cambias, más estrecha es la ventana en que una fuga hace daño.
Un patrón operativo ayuda a que rotar no dé miedo: introduce la clave nueva antes de retirar la vieja cuando el proveedor lo permite, de modo que ambas sean válidas durante una ventana. Así despliegas el valor nuevo, verificas que todo funciona, y solo entonces revocas el anterior, sin un instante en que el Worker se quede sin credencial válida.
También puedes crear, rotar y borrar secretos desde el panel de Cloudflare, sin tocar Wrangler; es útil para quien opera sin CLI. El valor se muestra siempre como cifrado, con la misma propiedad de solo escritura. Un consejo: elige un flujo —CLI o panel— y sé consistente. Mezclar ambos sin disciplina lleva a la confusión sobre qué valor está realmente desplegado, que es justo la incertidumbre que un secreto de solo lectura ya introduce de por sí.
Detente en la propiedad de solo escritura, porque encierra toda una filosofía de seguridad. Que ni siquiera tú, dueño de la cuenta, puedas volver a leer un secreto una vez guardado parece una molestia hasta que entiendes lo que compra: elimina la lectura como superficie de ataque. No hay endpoint que devuelva el valor, luego no hay endpoint que un atacante pueda engañar para que lo devuelva; no hay pantalla que lo muestre, luego no hay captura ni sesión comprometida que lo revele. El valor solo fluye en una dirección, del almacén al runtime, y ese camino está cifrado de punta a punta. Interioriza entonces que un secreto no se define por su contenido sino por su ausencia de todos los lugares equivocados: no está en el código, no está en el manifiesto versionado, no está en los logs, no está en tu historial de shell, no se puede leer de vuelta. La seguridad de una credencial no es una propiedad que se añade —ciframos el valor— sino una que se resta: cada lugar del que consigues que el secreto esté ausente es un lugar que ya no tienes que vigilar. Programar con secretos bien puestos es, en el fondo, un ejercicio de sustracción disciplinada: llevar el material sensible al menor número posible de sitios y, en cada uno de ellos, poder argumentar por qué ahí sí y por qué de ahí no se escapa.
- Parte de un Worker que tiene una clave de API escrita como constante en el código. Súbela con
wrangler secret puty bórrala del código; léela ahora desdeenv. - Ejecuta
wrangler secret listy observa que solo ves el nombre. Intenta encontrar el valor: convéncete de que el almacén es de solo escritura. - Crea un
.dev.varscon una clave de prueba, verifica quewrangler devla usa, y comprueba que el archivo está ignorado por git. - Diseña, en pseudocódigo o en tu YAML de CI, cómo inyectarías el secreto en el despliegue sin escribirlo en el repositorio. Nombra de dónde saldría el valor y con qué token te autenticarías.
- Rota uno de tus secretos con un segundo
wrangler secret putdel mismo nombre y confirma que el Worker desplegado empieza a usar el valor nuevo sin que cambies una línea de código.