El buscar-y-reemplazar que corrompió 300 archivos
Las bases de código impulsadas por agentes se mueven rápido, y fallan de formas que un revisor humano habría atrapado de un vistazo. Aquí hay dos fallos, en público, y las barreras de seguridad que ahora los detienen en seco.
El día en que sales a buscar problemas suele ser el día en que encuentras algunos. Este sábado programé una auditoría profunda de la base de código. El plan era ponerse al día con deuda técnica: bloques except desnudos, constantes codificadas a fuego, deriva en convenciones, el tipo de pila que se acumula en cualquier proyecto que corre a velocidad.
Encontré dos cosas que no disfruté. Ambas ya están arregladas. Ambas son el tipo de hallazgo sobre el que quiero ser público, porque explican algo real sobre cómo falla una base de código impulsada por agentes y cómo la disciplina de hacer una auditoría atrapa los fallos.
Hallazgo uno: el buscar-y-reemplazar que se comió la base de código
Antes en el proyecto, se le había pedido a un agente que hiciera un barrido de estilo de marketing a través de docstrings y comentarios. La intención era razonable: renombrar una frase específica que aparecía en algunas cadenas orientadas al público. La ejecución no fue lo bastante cuidadosa con los límites de palabra.
La frase que se suponía que el agente intercambiara era un eslogan particular de marketing que contenía las palabras «process», «success», y «access» como parte de frases más largas. La operación de buscar-y-reemplazar coincidió con esas subcadenas en lugares donde no debía coincidir. Nombres de variables. Nombres de funciones. Descripciones de pruebas. Comentarios en línea. En cualquier lugar donde aparecían esas tres subcadenas, se sustituyó la cadena de reemplazo del agente.
El resultado fueron trescientos archivos con identificadores y prosa sutilmente garabateados. Variables llamadas process_event se convirtieron en algo con «Raku Game Engine Milestone» incrustado en medio del token. Las descripciones de funciones se leían como sinsentido. Las descripciones de pruebas afirmaban estar probando cosas que no existían. La base de código compilaba porque los identificadores rotos eran consistentes dentro de sus archivos, pero la capa legible por humanos de la base de código estaba vandalizada en lugares sutiles por todas partes.
Quiero ser específico sobre cómo ocurre este tipo de fallo porque es una clase de fallo impulsado por agentes que otros equipos van a encontrar.
La búsqueda estaba acotada demasiado ampliamente. Se le indicó al agente que encontrara una frase y la reemplazara. Resultó que la frase era una subcadena de palabras comunes en inglés. La forma correcta de acotar esta búsqueda es por límites de palabra (\bpalabra\b en regex), con sensibilidad a mayúsculas explícita, con una lista de extensiones de archivo permitidas explícita, con una lista de contextos de identificador prohibidos explícita. La instrucción que recibió el agente no tenía ninguna de esas restricciones.
El agente no marcó la amplitud. Trescientos archivos son muchos archivos. Un agente que aterriza una PR que toca trescientos archivos por un pequeño ajuste de marketing debería haber marcado la amplitud al momento de abrir la PR. El agente no lo hizo. El título de la PR decía algo como «update marketing copy in docstrings». El cuerpo de la PR listaba el conteo de archivos como un número, no como una preocupación.
Mi proceso de revisión no lo atrapó. El diff de la PR eran trescientos archivos de pequeños cambios de dos líneas que todos se veían como la misma edición. El diff se lee, en una hojeada, como un barrido limpio. La corrupción solo se muestra si lees el contenido real cambiado de un archivo en el momento en que la sustitución del agente produjo sinsentido. No lo hice. Fusioné.
El CI no lo atrapó porque los nombres seguían parseando. Los identificadores corrompidos eran sintácticamente válidos. A los compiladores no les importa si tu variable se llama algo que parece un eslogan de marketing. La compilación estaba en verde. Las pruebas seguían corriendo. El daño estaba en la capa humana del código, no en la capa de la máquina.
Cómo lo arreglé este sábado
Un script. El script hace tres cosas.
Uno: re-derivar los nombres canónicos de identificadores. Del historial de git antes de que aterrizara el mal buscar-y-reemplazar, el script reconstruye cómo se suponía que debía llamarse cada identificador. La reconstrucción es mecánica: para cada archivo tocado por la mala PR, compara la versión previa a la PR contra la versión posterior, y para cada token sustituido, propone una restauración al nombre previo a la PR. La mayoría de los archivos se restauran limpiamente. Un pequeño número necesita revisión manual porque tenían cambios legítimos superpuestos a la corrupción.
Dos: una pasada de sanidad impulsada por grep. Incluso después de la restauración, algunos de los identificadores corrompidos habían sido referenciados desde código nuevo escrito después de que aterrizara la mala PR. Esas referencias habían sido escritas contra los nombres corrompidos. La pasada de grep encuentra cada referencia a un identificador de estilo corrompido en código escrito después de que aterrizó la mala PR, y marca cada una para decisión manual: ¿este código nuevo pretendía usar el nombre corrompido (raro), o simplemente estaba usando el nombre que sea que existiera en ese momento (la mayoría de los casos)?
Tres: una salvaguarda para el futuro. Cada operación de buscar-y-reemplazar que hace un agente ahora tiene que especificar (a) acotación por límite de palabra, (b) sensibilidad a mayúsculas, (c) lista de extensiones de archivo permitidas, (d) un umbral máximo de conteo de archivos más allá del cual el agente tiene que marcar y pedir revisión explícita, y (e) una muestra de tres coincidencias aleatorias que el agente tiene que mostrar antes de aplicar el reemplazo completo. La salvaguarda está en la Guía de Copilot y ahora es parte del enmarcado de cada tarea de buscar-y-reemplazar.
La corrupción ya está reparada. El script de auditoría que hizo la reparación está en el repositorio, ejecutable en cualquier momento, con las salidas de diff guardadas como evidencia. La lección está en la Guía de Copilot.
Hallazgo dos: el secreto HMAC codificado a fuego
La auditoría profunda también encontró algo que debería haber atrapado antes. La capa de licenciamiento del runtime usa HMAC-SHA-256 para verificar tokens de licencia. El secreto HMAC estaba codificado a fuego en un archivo fuente. El archivo fuente estaba en el repositorio público. El secreto era un secreto real usado por una ruta de verificación de producción real.
Este es el hallazgo más vergonzoso del día. Quiero ser honesto al respecto porque es exactamente el tipo de cosa que ocurre en bases de código impulsadas por agentes que se mueven rápido, y la discusión pública de cómo atraparlo es más valiosa que la discusión privada.
El camino que tomó para aterrizar: Una versión temprana de la capa de licenciamiento se prototipó con un valor de secreto de marcador de posición, con la intención de reemplazarse antes de que la capa se enviara a nadie. El prototipo aterrizó en una PR con un marcador de posición de desarrollo de apariencia obvia. Con el tiempo, se agregó lógica de verificación real encima del marcador de posición. El marcador de posición dejó de parecer un marcador de posición una vez que quedó envuelto en código de validación de apariencia real. Para cuando alguien lo notó, el secreto se estaba usando en flujos de estilo de producción y el archivo estaba en el repositorio público.
Lo que hice hoy:
- Roté el secreto. El valor comprometido ya no es el valor de producción. El valor nuevo está en una variable de entorno, con un fallback de
warnings.warn()para entornos de desarrollo que permite que el trabajo de desarrollo continúe sin un secreto real pero que grita ruidosamente sobre ello. - Eliminé el valor codificado a fuego del archivo fuente. El reemplazo es un
getenvcon un mensaje de error claro si la variable de entorno no está definida en una compilación de producción. - Agregué una verificación de CI que escanea en busca de secretos codificados a fuego que coincidan con patrones comunes (cadenas de alta entropía, tokens con forma de base64, cualquier cosa que parezca una clave). La verificación es el tipo de pequeña infraestructura que atrapará el próximo intento antes de que aterrice.
- Archivé un seguimiento para auditar el resto de la base de código en busca de patrones similares. La auditoría es el trabajo de un fin de semana separado. Hoy se trató de cerrar el hallazgo inmediato.
La capa de licenciamiento todavía funciona. La ruta nueva es más segura. El secreto comprometido se rotó a horas de su descubrimiento.
A qué se generaliza esto
Algunos puntos honestos.
El buscar-y-reemplazar impulsado por agentes necesita reglas de acotación explícitas. Esta es la tercera vez en la historia del proyecto que un barrido demasiado amplio me muerde. Las primeras dos veces fueron menos dañinas. Esta vez fue lo bastante mala como para merecer una barrera de seguridad permanente. La barrera ya está en su lugar.
Los secretos codificados a fuego en archivos fuente son un fallo de disciplina, no un fallo de herramientas. Ninguna herramienta va a salvar a un equipo que deja que un secreto real aterrice en un archivo público. La disciplina de «cada commit se revisa en busca de credenciales codificadas a fuego» es el arreglo real. El escaneo de CI ayuda. La disciplina es lo que importa.
Las auditorías encuentran lo que la revisión pasó por alto. La disciplina de correr una auditoría programada sobre la base de código, buscando específicamente los modos de fallo que la revisión PR por PR tiende a pasar por alto, vale el tiempo. La auditoría de hoy atrapó dos cosas que la revisión de PR había dejado pasar. Las auditorías futuras atraparán otras cosas. La cadencia es el punto.
Lo que socios y constructores deberían llevarse de esto
Si estás evaluando un motor para una asociación, pregúntale al equipo cómo manejan el modo de fallo de «barrido demasiado amplio impulsado por agentes». La respuesta correcta involucra reglas de acotación explícitas, marcado obligatorio en cambios grandes, y pasadas de auditoría. La respuesta equivocada es «no hemos visto ese problema».
Si tú mismo estás corriendo un flujo de trabajo impulsado por agentes y no has hecho una auditoría de secretos codificados a fuego recientemente, haz una. La probabilidad de que algo se haya colado no es cero. El costo de encontrarlo ahora es pequeño.
Si eres un profesional de seguridad leyendo esto y tienes sugerencias, estoy genuinamente interesado. La clase de fallo contra la que estoy trabajando para defenderme es «el agente hace algo que un revisor humano habría atrapado de un vistazo pero no atrapó en el patrón de revisión masiva que fomenta un flujo de trabajo impulsado por agentes». Sugerencias bienvenidas.
Tarde de sábado. A la base de código se le dio una mirada dura. Dos hallazgos, ambos arreglados. La próxima auditoría está en el calendario.
De vuelta a construir.
Un runtime construido para ser auditado
RakuAI es el runtime espacial con el que construyen los fabricantes de modelos de IA y los fabricantes de gafas inteligentes: disciplinado por auditorías, endurecido por lecciones públicas. Descubre cómo diseñamos para la confianza de nivel socio.