Una carpeta de instrucciones y una caja cerrada separadas por un panel naranja junto a una maqueta arquitectónica.

Configuración y secretos: lo que nunca debe quedar dentro del código

El repositorio de un ejemplo debería permitir que otra persona entienda cómo funciona. No debería permitirle utilizar tu cuenta. La diferencia parece bastante evidente, pero basta con que una credencial ayude a terminar una demostración para que empiece a tratarse como parte del código. Luego llega el primer ZIP compartido y descubrimos que también hemos distribuido una pequeña delegación de confianza, sin haberlo puesto en el README.

Vamos a separar la configuración de una aplicación y sus secretos con un programa Python que lee dos variables de entorno. Comprobaremos la ausencia de datos obligatorios, prepararemos una plantilla compartible y definiremos qué debe ignorar Git. Todo se probará con una credencial ficticia sin validez. El programa no llamará a ninguna API: que una cadena esté presente no demuestra que un proveedor la acepte, y no necesitamos hacer esa llamada para aprender a manejarla correctamente.

Tabla de contenidos

Una dirección configurable y una credencial no tienen el mismo valor

La URL pública de una API puede variar entre desarrollo y producción sin ser un secreto. Una credencial, en cambio, puede conceder capacidad de actuar o consultar datos. Ambas pueden llegar al programa mediante configuración, pero sus requisitos de acceso y distribución son distintos. No pondría el mismo tratamiento a una dirección pública y a algo que permite utilizar una cuenta ajena solo porque los dos valores caben entre comillas.

En un proyecto BIM podrías necesitar la dirección del backend, un identificador público de aplicación y un secreto que solo conoce el servidor. El nombre del campo no basta para decidir si es confidencial: hay que revisar qué representa y qué permite hacer. Tampoco todo identificador es una credencial ni todo token tiene el mismo alcance. La distinción entre autenticación y autorización ayuda a no confundir reconocer una aplicación con concederle acceso a cualquier recurso.

Separar estos valores del código permite ejecutar la misma implementación con configuraciones diferentes. La aplicación debe declarar qué necesita, cómo valida lo recibido y qué hace cuando falta. Un valor de ejemplo puede documentar el formato; una credencial real no debe convertirse en un valor predeterminado que entra en acción cuando alguien olvida configurar el servicio. La comodidad de ese fallback se la acabas cobrando a otra cuenta.

También conviene distinguir el código del servidor del que llega al navegador. Una variable de compilación incluida en JavaScript descargable puede quedar visible para quien recibe la aplicación. Llamarla «de entorno» no la convierte en confidencial. Si una operación necesita un secreto del backend, ese secreto debe mantenerse en el componente que puede custodiarlo; el navegador no adquiere una caja fuerte porque el nombre de la variable lleve mayúsculas.

Leer el entorno y fallar sin imprimir lo que intentabas proteger

Usaremos BIM_API_URL para una dirección base HTTPS y BIM_API_TOKEN para una cadena obligatoria. El nombre es propio del ejercicio, no una variable oficial de Autodesk. La URL no aceptará usuario o contraseña incorporados, consulta ni fragmento. El token deberá contener algo distinto de espacios y no tendrá saltos de línea. No estamos validando el formato ni la autenticidad de una credencial de un proveedor concreto.

Guarda este programa como config_demo.py. Funciona con Python 3.10 o posterior y la biblioteca estándar:

				
					import os
from urllib.parse import urlsplit


class ConfiguracionError(ValueError):
    pass


def cargar_config(entorno):
    url = entorno.get("BIM_API_URL", "").strip()
    if not url:
        raise ConfiguracionError("falta BIM_API_URL")
    if any(caracter.isspace() or ord(caracter) < 32 for caracter in url):
        raise ConfiguracionError("BIM_API_URL contiene caracteres no admitidos")
    try:
        partes = urlsplit(url)
        puerto = partes.port
    except ValueError:
        raise ConfiguracionError("BIM_API_URL no tiene una forma admitida") from None
    if (partes.scheme != "https" or not partes.hostname or
            partes.username is not None or partes.password is not None or
            partes.query or partes.fragment or puerto == 0):
        raise ConfiguracionError("BIM_API_URL requiere HTTPS sin credenciales ni consulta")
    token = entorno.get("BIM_API_TOKEN", "")
    if not token.strip():
        raise ConfiguracionError("falta BIM_API_TOKEN")
    if "\r" in token or "\n" in token:
        raise ConfiguracionError("BIM_API_TOKEN contiene saltos de linea")
    return {"api_url": url, "api_token": token}


if __name__ == "__main__":
    try:
        configuracion = cargar_config(os.environ)
    except ConfiguracionError as error:
        print("Configuración incorrecta:", error)
        raise SystemExit(2)
    print("Configuración cargada; no se ha realizado ninguna petición.")
				
			

La función recibe un mapa de textos. Eso permite probarla con un diccionario preparado, sin depender de las variables reales del equipo. Al ejecutar el archivo utilizamos os.environ, que ofrece el entorno del proceso. Los mensajes indican el nombre del campo y el problema, pero no interpolan su valor. El programa tampoco imprime el diccionario resultante: ahí dentro está precisamente el dato que hemos decidido custodiar.

La URL se recorta por los extremos como decisión de configuración; el token se conserva tal como se recibe. No aplicaríamos una limpieza arbitraria a una credencial opaca. Por otra parte, urlsplit divide una URL en componentes y no hace una validación de seguridad completa. Estas comprobaciones solo definen las formas admitidas en el ejercicio. No demuestran que el destino exista, que sea el servidor adecuado ni que resulte seguro aceptar una dirección proporcionada por un usuario.

Ejecutar el ejemplo sin convertir el valor de prueba en una credencial real

En una terminal PowerShell situada en la carpeta del archivo, puedes ejecutar este bloque:

				
					$env:BIM_API_URL = 'https://api.example.com'
$env:BIM_API_TOKEN = 'solo-demostracion-sin-validez'
python config_demo.py
				
			

La salida será «Configuración cargada; no se ha realizado ninguna petición.». El dominio sirve como dirección de ejemplo y el token es una cadena ficticia: ninguno acredita acceso a una API. La asignación con $env: afecta al proceso de PowerShell y al entorno que reciben sus procesos hijos, como explica la documentación de PowerShell. No hemos guardado estos valores como configuración permanente del sistema.

Si ejecutas el programa desde un entorno donde falte alguna variable obligatoria, terminará con código de salida 2 y el nombre de la que falta. Eso es preferible a arrancar aparentemente bien y fallar al hacer la primera consulta. En una aplicación mayor, la validación debería ocurrir al preparar el componente que necesita esa configuración, antes de permitir una operación que dependa de ella.

Este modo de escribir valores en la terminal es adecuado para nuestra cadena ficticia. No lo tomaría como procedimiento para introducir secretos reales: pueden quedar en el historial, una transcripción o una captura. En desarrollo y despliegue tendrás que elegir un mecanismo apropiado de entrega del secreto y limitar quién puede verlo. Tampoco las variables de entorno son almacenamiento cifrado por definición; son una forma de poner un valor a disposición de un proceso.

Si prefieres archivos .env, recuerda que este programa no los carga automáticamente. Necesitarías configurar una herramienta que los lea o incorporar ese comportamiento de forma explícita. Copiar un .env.example y cambiarle el nombre no hace que os.environ descubra su contenido. Antes de perseguir un error de credenciales, comprueba qué mecanismo está entregando realmente la configuración al proceso.

Compartir la plantilla y comprobar qué entra en Git

Podemos guardar una plantilla .env.example con los nombres esperados y sin credenciales:

				
					BIM_API_URL=https://api.example.com
BIM_API_TOKEN=
				
			

El token queda vacío deliberadamente. La plantilla sirve para documentar la configuración que hace falta y puede acompañar al código descargable; no es una configuración lista para acceder a un servicio. En el README explicaría dónde se obtiene cada valor y qué componente debe recibirlo, sin copiar secretos de una instalación que ya funciona. Una persona que clona un ejemplo necesita instrucciones, no acceso prestado a la cuenta del autor.

Si el proyecto utiliza archivos .env locales, este fragmento de .gitignore evita añadirlos por el recorrido normal de Git y conserva la plantilla:

				
					.env
.env.*
!.env.example
				
			

Las reglas se leen en orden: la última vuelve a permitir el archivo de ejemplo. Según la documentación de gitignore, estos patrones afectan a archivos que todavía no están seguidos. No eliminan del índice o del historial un archivo que ya se añadió. Además, una incorporación forzada puede saltarse la exclusión. El archivo de ignore ayuda a evitar un error de distribución; no demuestra por sí solo que un repositorio esté libre de secretos.

En un repositorio Git, puedes inspeccionar el efecto de las reglas y qué archivos están preparados para el siguiente commit:

				
					git status --short --ignored
git diff --cached --name-only
				
			

Revisaría también el contenido que se va a compartir, no solo los nombres. Una credencial puede estar en un cuaderno, una prueba, una salida de consola, una copia de configuración o una captura. Y si distribuyes un ZIP desde una carpeta, el mecanismo que lo crea puede incluir archivos ignorados por Git. Antes de ofrecer el descargable, hay que revisar ese artefacto concreto. El repositorio y la carpeta del equipo no son necesariamente la misma colección de archivos, por mucho que estén mirando el mismo escritorio.

Un secreto también necesita una retirada y un responsable

En una aplicación desplegada, cada entorno debería recibir los valores que le correspondan y conceder solo las capacidades necesarias. La guía de gestión de secretos de OWASP trata su almacenamiento, acceso y ciclo de vida. Un gestor de secretos puede ayudar con la distribución y la rotación; no sustituye decidir qué proceso necesita cada credencial ni quién puede administrarla.

Si una credencial real se ha publicado, borrarla del último archivo no basta. Primero hay que revocarla o sustituirla en el servicio que la acepta y revisar su posible uso. Después se corrigen las copias y la distribución, coordinando cualquier limpieza de historial que haga falta. Reescribir el historial no garantiza retirar las copias que otras personas ya descargaron. Esa es la razón por la que la respuesta al incidente debe actuar sobre la credencial, no limitarse a mejorar la apariencia del repositorio.

También conviene prever la sustitución normal antes de que haya un incidente: quién cambia el valor, cómo lo recibe la aplicación y cómo comprobamos que el anterior ya no concede acceso. Que el nuevo secreto esté guardado no significa que todos los procesos hayan dejado de utilizar el viejo. El mecanismo de configuración y el ciclo de vida del servicio tienen que encontrarse en algún momento, preferiblemente antes del primer error de producción.

El ejemplo deja una separación verificable: el código declara lo que necesita, la plantilla muestra los nombres y el entorno aporta los valores al ejecutar. El programa falla si falta algo, y su salida no revela la credencial. Mantendría esa separación al crecer la aplicación. Un tutorial puede enseñar el recorrido completo sin repartir las llaves; de hecho, lo considero una mejora bastante razonable del concepto de material descargable.

Artículos relacionados

Respuestas

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *