Bitácora

La puerta antes del commit

Un secreto que llega a un remoto es público. Rotarlo es el único arreglo de verdad, y cada hora entre el push y la rotación es exposición que ya no puedes…

Un secreto que llega a un remoto es público. Rotarlo es el único arreglo de verdad, y cada hora entre el push y la rotación es exposición que ya no puedes retirar. Así que el sitio útil para un escáner no es la CI, ni un hook que instalas por máquina: es dentro del comando que escribe el commit.

staged files the index scanner AKIA… ghp_… glpat- sk_live PEM blocks · JWTs user:pass@host + .toriignore [secrets] *.example · *.sample · *.template skip the gate commit written object enters history commit aborted file and line printed override with --yes torii scan --history runs the same patterns over every commit already written torii history replace-text --redact-secrets rewrites what it finds

Dónde se sitúa

torii save escanea el contenido indexado antes de crear el objeto commit. Cuando hay un hallazgo no se escribe nada: el comando aborta e imprime el fichero y la línea. No hay hook que instalar, ni configuración por repositorio, ni manera de acabar en una máquina nueva con la protección apagada sin enterarte.

torii scan --history pasa los mismos patrones por todos los commits ya escritos, para la pregunta «¿esto llegó a ser cierto alguna vez?».

La forma que se colaba

Hasta 0.13.0, los patrones de token casaban con una clave que apareciera como palabra suelta delimitada por espacios. Esa es la forma que tiene una clave en prosa, y casi nunca la que tiene dentro de un fichero.

GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx

Ahí la asignación entera es una sola palabra, y no empieza por ghp_. Cada fichero .env, cada argumento -e KEY=value de un contenedor y cada variable de CI pegada en un compose era invisible. El mismo agujero cubría claves de AWS, tokens de GitLab, Stripe y SendGrid.

Se encontró escribiendo la forma fea y probándola, no leyendo la lista de patrones y concluyendo que parecía completa. Esa es toda la lección: una regla de detección vale lo que vale la entrada real más fea contra la que la hayas probado.

Los placeholders, y por qué se fue la lista de palabras

La regla de cadenas de conexión solía saltarse cualquier contraseña que fuera password, pass, changeme, xxx o yourpassword, con la teoría de que son ejemplos.

También son contraseñas que la gente usa de verdad. Una regla que se salta las credenciales más débiles del corpus es peor que no tener regla. Ahora solo se excluyen los placeholders sintácticos — ${VAR}, $VAR, <placeholder> — que son formas que ninguna contraseña puede tener.

El coste es que un ejemplo de documentación con user:pass@localhost ahora salta. Es el intercambio correcto: un falso positivo cuesta un --yes; un falso negativo cuesta una rotación y un incidente.

Otras dos que estaban mal

Un scheme://user@host pelado —sin contraseña alguna, la URL al estilo SSH— se reportaba como credencial filtrada. Ahora comprueba que el userinfo lleve de verdad user:password@.

Y a la lista de esquemas le faltaban postgres:// y mongodb+srv://, así que una credencial real escrita en cualquiera de las dos grafias más comunes era invisible sin importar su longitud.

Cuando el hallazgo no es real

torii save -am "docs: example" --yes

O, de forma permanente, una entrada de allowlist en .toriignore, o // torii:allow-secret en la línea. --yes también hace falta cuando stdin no es un terminal: en CI o bajo un agente el escáner no puede preguntar, y falla rápido señalando la opción en vez de colgarse esperando una respuesta que nadie puede dar.