Seguridad a nivel de tipos: ¿el futuro de la generación segura de código con IA?
4 de junio de 2026
0 minutos de lecturaIntroducción
Como el código se escribe (y se genera) más rápido que nunca, también aparecen vulnerabilidades de seguridad a un ritmo sin precedentes. Pedirle a tu LLM que no incluya vulnerabilidades de seguridad en su código no siempre funciona. Cada vez está más claro que la forma en que se crea el software hoy, de manera manual o asistida, no basta para escribir código seguro de manera confiable, consistente y comprobable.
El auge de Rust ha demostrado que es posible eliminar por completo clases enteras de vulnerabilidades de manera práctica y ergonómica. Rust ha eliminado eficazmente todas (o casi todas) las vulnerabilidades de corrupción de memoria en tiempo de compilación. Entonces, ¿por qué limitarnos a esta clase de vulnerabilidades?
En esta publicación, explicaré cómo es posible escribir código de tal manera que las vulnerabilidades de seguridad de las aplicaciones web sean incompilables (o no puedan pasar la verificación de tipos), y cómo las bibliotecas seguras desde el diseño o los envoltorios de bibliotecas bien ubicados pueden impedir por completo que se escriban clases enteras de vulnerabilidades, ya sea manualmente o con un LLM. Mostraré patrones de código en Python y Rust que podrían usarse para eliminar clases de vulnerabilidades.
Por qué los tipos
Cuando se usan correctamente, los sistemas de tipos pueden ser herramientas increíblemente poderosas. Te permiten codificar las invariantes de un sistema; es decir, todas las reglas, propiedades y relaciones de cada dato de tu programa. En lugar de dejar un comentario útil o realizar aserciones en tiempo de ejecución (que quizá solo se activen cuando tu primer cliente intente hacer algo importante), puedes asegurarte de que cada llamada a tu función add solo pase dos tipos int, en todos los casos y en toda la base de código.
Por experiencia, el código que escribo con tipos extremadamente estrictos tiene muchos menos errores en tiempo de ejecución que el código que escribo sin ellos, porque me veo obligado a razonar sobre cada parámetro, cada dato y cada entrada. Cuando codifico todo como corresponde, cometo muchos menos errores y mi código solo tiene errores de lógica de negocio, no errores como «ups, pasé una cadena a una función que suma enteros».
Muchas vulnerabilidades de seguridad son simplemente un tipo específico de error y, si aceptamos lo que afirmé antes, muchos de esos errores deberían poder resolverse con un sistema de tipos lo suficientemente flexible.
Tipos confiables
Esta publicación se inspira, en parte, en la API web Trusted Types. Esta API mitiga eficazmente la mayoría de las formas de XSS del DOM al garantizar que los puntos de inserción de XSS solo acepten valores conocidos como seguros, como los que depura un sanitizador de XSS adecuado. Es posible reforzar aún más esta API con CSP para exigir el uso de Trusted Types, que genera un TypeError si no se usa.
El kernel de Linux usa un mecanismo similar con la macro __user, que garantiza que los punteros de espacio de usuario se manejen correctamente.
En las siguientes secciones, veremos cómo generalizar esta técnica y aplicarla a clases arbitrarias de vulnerabilidades de seguridad.
Cómo resolver IDOR
La referencia directa insegura a objetos (IDOR) es una vulnerabilidad persistente que se origina en la falta de controles de autenticación o autorización al realizar acciones mediante una API. El ejemplo clásico es un endpoint de API que recibe como parámetro un ID de usuario incremental y devuelve sus datos. Sin embargo, al cambiar el ID de usuario, devuelve los datos de otro usuario, sin los controles de autorización adecuados.
Según las estadísticas, es probable que tú, lector, uses Python, así que primero exploraremos esta vulnerabilidad y cómo resolverla en Python usando solo sugerencias de tipos.
En Python
Empecemos con un ejemplo vulnerable:
Esta llamada a la API tiene un aspecto bastante estándar: pasas un ID de usuario entero, consultas la base de datos y devuelves el resultado en la respuesta. ¡Pero vaya! Olvidamos agregar controles de autenticación o autorización; cualquier usuario puede solicitar el saldo de cualquier otro usuario (si conoce su ID): un caso clásico de IDOR. Recibimos el informe de la vulnerabilidad, pagamos la recompensa al investigador y actualizamos la función de esta manera:
Perfecto, los datos del usuario están seguros. Pero es un error muy fácil de cometer. Olvidar un control de autenticación en un solo endpoint de API puede tener consecuencias importantes. Ahora veamos el mismo endpoint, pero usando el sistema de tipos para asegurarnos de que esta vulnerabilidad no vuelva a ocurrir:
Aquí hay bastante más código, pero en el panorama general quedaría integrado en tu código de autenticación y autorización.
El principal cambio en este nuevo código es que nunca pasamos tipos básicos (como el ID de usuario int de los ejemplos anteriores). Los datos de entrada se abstraen en una clase opaca a la que solo puedes acceder demostrando que ya realizaste los pasos de autenticación y autorización. Al asegurarte de que tu clase UncheckedUserID no pueda hacer nada útil (como usarse en una expresión SQL), el verificador de tipos detectará de forma confiable el error si olvidaste realizar la comprobación de autenticación para extraer el valor «real» de user_id.
Como Python es un lenguaje dinámico, técnicamente podrías omitir la comprobación y acceder al valor interno sin proporcionar un AuthenticationGuard válido, pero es de esperar que en la revisión de la solicitud de cambios se identifique ese código como indeseable. Otros lenguajes, como Rust, pueden ofrecer garantías mucho más sólidas, lo que significa que ni siquiera con código poco elegante puedes acceder directamente al valor interno de la clase envoltorio. Obviamente, el código sigue ejecutándose, así que podrías tomar medidas extremas, como volcar la memoria de la instancia. Pero si solo quieres asegurarte de que la autenticación sea consistente, ¿por qué harías algo así?
En Rust
Los especificadores de visibilidad de Rust ofrecen un control estricto sobre qué código puede acceder al valor user_id envuelto, lo que hace que esta sea una forma aún más confiable de garantizar los controles de autenticación y autorización. En el ejemplo de abajo, usamos Axum con un extractor para asegurarnos de que el valor de entrada se deserialice correctamente en nuestra clase envoltorio de seguridad.
Viabilidad
Obviamente, este método para mitigar vulnerabilidades de seguridad requiere un compromiso considerable, sobre todo en proyectos de software ya establecidos. Se necesitaría infraestructura de desarrollo para garantizar que estos patrones se sigan de manera consistente. Las dos formas principales de lograrlo serían mediante bibliotecas envoltorio o reglas personalizadas de lint.
Tomemos como ejemplo el caso de Python. Como organización, podrías exigir el uso de la biblioteca MyOrgFastAPI, un envoltorio liviano de FastAPI, y asegurarte de que todos los endpoints de API usen tipos personalizados que cumplan estos requisitos de seguridad. Ningún endpoint debería aceptar un argumento str; tendría que ser algún tipo de UncheckedString.
Como alternativa, si no quieres mantener una biblioteca envoltorio personalizada, podrías implementar estas reglas en el linter. Aunque así no se ofrece retroalimentación al desarrollador tan pronto, sí puede brindar la misma protección al prohibir los tipos básicos.
Sea cual sea la forma de lograrlo, te asegurarías de que cada caso se maneje correctamente. Este patrón de código se puede extender de forma natural para proteger contra todo tipo de vulnerabilidades:
Un
UncheckedStringdebe sanitizarse correctamente para obtener una cadena sin procesar que se devuelva al usuario, lo que mitiga XSSUn
UncheckedStringno se puede concatenar a una consulta SQL, lo que mitiga las vulnerabilidades clásicas de SQLUn
UncheckedStringno se puede concatenar a una cadena de comandos, lo que mitiga las vulnerabilidades de inyección de comandos.
Y la lista continúa.
Estos patrones también se aplicarían por igual a desarrolladores humanos y agentes de IA. Pedirle a un agente de IA que aplique la autenticación en todas partes quizá no sea 100 % confiable, pero exigirla en la etapa de compilación o lint puede ser muy eficaz.
Conclusión
Implementar la seguridad de esta manera, a nivel de tipos, puede ser una herramienta increíblemente poderosa tanto para desarrolladores humanos como para agentes de IA. Aunque al principio puede requerir un esfuerzo adicional, sobre todo en proyectos de software existentes, los beneficios pueden ser considerables, ya que permite eliminar clases enteras de vulnerabilidades.
La forma de manejar los datos puede variar mucho de un proyecto a otro, pero pensar en ello desde el principio puede dar grandes beneficios para crear código seguro.
DOCUMENTO TÉCNICO
La crisis de seguridad de IA en tu entorno de Python
Con el ritmo de desarrollo acelerándose a un ritmo vertiginoso, ¿sabes realmente a qué puede acceder tu entorno de IA?
