Seguridad

Secretos a la vista: cómo una línea de código puede matar un proyecto

Por qué incrustar credenciales es una bomba de tiempo, y cómo una gestión disciplinada de variables de entorno salva proyectos, presupuestos y nervios.

5 min de lecturaJunio 2026

Imagina una situación clásica: viernes por la tarde, la fecha límite arde. Un desarrollador prueba rápido una integración, pega una clave de API directamente en un archivo de configuración para «ver si funciona», hace push y se va el fin de semana tranquilo. El lunes el equipo se lleva una sorpresa: accesos comprometidos, datos filtrados y una factura de la nube que parece un número de teléfono.

El motivo es banal: el secreto se quedó en el código y voló al historial del repositorio.

En PolyCode Systems trabajamos a diario con infraestructuras complejas, integraciones de marketplaces y sistemas multiagente. El coste de un error es demasiado alto. Por eso nuestra primera y más estricta regla es: ningún secreto en el código. Nunca. Ni «por cinco minutos» ni «solo para pruebas locales». Hoy hablamos de por qué incrustar datos confidenciales es siempre una bomba de tiempo.

Por qué la ilusión de seguridad cuesta tan caro

Muchos piensan «es un repo privado» o «comentaré esa línea antes del commit». En la práctica ese enfoque acaba fallando. Estas son las tres trampas principales:

  1. Git no olvida nada. Si por accidente subes un archivo con la contraseña de la base de datos o una clave de pasarela de pago, borrarlo en el siguiente commit no sirve. El secreto queda para siempre en el historial de Git. A un atacante (o a un junior curioso) le basta con mirar el historial de cambios.
  2. El efecto copia-pega. Con la configuración incrustada, mover la app de Dev a Stage o Prod es un campo minado. Hay que cambiar claves a mano en cada despliegue. Una errata, y el servidor de pruebas empieza a cobrar dinero real a la cuenta de producción.
  3. Fugas por capturas y logs. Lo incrustado aparece en logs de error, trazas o en pantalla durante el pair programming. Las variables de entorno permanecen invisibles para el propio código — viven a nivel de servidor.

El estándar de oro de PolyCode Systems

Hemos desarrollado un proceso simple pero sin concesiones. No requiere caras herramientas enterprise, sino disciplina de hierro.

  • El archivo .env existe, pero no está en el repo. En la raíz siempre hay un .env.example: una plantilla con todas las variables necesarias (p. ej. DB_HOST=, STRIPE_API_KEY=) sin valores. El desarrollador lo copia, lo renombra a .env y pone sus claves. El propio .env está estricta y permanentemente en .gitignore.
  • Acceso aislado. El código no debe saber dónde se ejecuta. Solo pide al sistema: «dame el token de la base de datos». En local lo da un archivo local; en producción, los ajustes de entorno vía la interfaz de infraestructura (p. ej. Coolify o las variables del servidor Linux).
  • Claves en CI/CD. En la compilación y el despliegue automáticos, los secretos pasan solo por los ajustes seguros del pipeline (p. ej. GitHub Secrets), que los inyectan directamente en el build. Sin enviar archivos de configuración en archivos comprimidos.

De la teoría a la práctica

Veamos el antipatrón clásico. Los principiantes suelen empezar con algo así:

El antipatrón

// ❌ How NOT to do it (a straight path to grey hair)
export const dbConfig = {
  host: "192.168.1.100",
  user: "admin",
  password: "SuperSecretPassword123" // Hello, public repository!
};

Parece que pasar a variables de entorno es solo cambiar cadenas por process.env.PASSWORD. Pero vamos un paso más allá. Leer process.env es la mitad. La otra mitad es la validación estricta al arrancar (fail fast). Si olvidaste la clave de la base, la app no debe arrancar. Mejor que falle ruidosamente al iniciar que arranque en silencio y devuelva errores 500 a usuarios reales. Este es el patrón en TypeScript (con zod) que se volvió nuestro estándar:

El estándar de oro: validar el entorno

// ✅ Gold standard: validate the environment
import { z } from 'zod';

// 1. Declare the variables we truly need
const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_SECRET_KEY: z.string().min(10),
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
});

// 2. Validate whatever the system gave us
const env = envSchema.safeParse(process.env);

if (!env.success) {
  console.error('❌ Critical error: missing environment variables', env.error.format());
  process.exit(1); // Hard-stop the application
}

// 3. Export a typed config for the whole project
export const config = env.data;

¿Dónde está la magia?

La belleza de esta arquitectura es que al código le da igual dónde se ejecuta. Usas un .env local en desarrollo y, al desplegar, pones las mismas claves en el panel del servidor. La lógica de negocio no cambia: ni una línea que editar.

Obtienes una confianza total: si la app arrancó, tiene todos los accesos que necesita y son válidos.

Una lista para tu equipo

Antes de cerrar el artículo, revisa tu proyecto con tres puntos:

  1. ¿Está .env añadido a .gitignore ahora mismo?
  2. ¿Hay un .env.example en la raíz, sin contraseñas reales, para que un nuevo desarrollador sepa qué configurar?
  3. ¿Tu app falla al arrancar si borras o renombras el archivo de configuración de producción?

Si todo es «sí», buen trabajo: tu código está a salvo. Si hay un «no», ya sabes qué hacer en el próximo sprint.

¿Y en tu equipo?

¿Cómo se entregan los secretos de los desarrolladores a DevOps en tu equipo? ¿Habéis tenido fugas épicas de claves en producción? Comparte tu historia en los comentarios.

Construimos productos sobre una infraestructura que se toma en serio los secretos.

Leer artículo