El impacto de la complejidad ciclomática en el desarrollo de software y la ciberseguridad
Entendiendo la complejidad ciclomática
Introducción
La complejidad ciclomática la definió Thomas J. McCabe en 1976, y sigue siendo una de las mejores formas de saber si tu código se te está yendo de las manos. Lo que no todo el mundo tiene en cuenta es la parte que le toca en ciberseguridad.
Así que en este artículo te cuento qué es exactamente, cómo afecta a la calidad del código y qué relación tiene con la seguridad. Con ejemplos de cómo un código enrevesado acaba convertido en una vulnerabilidad, y qué puedes hacer para evitarlo.
Entendiendo la complejidad ciclomática
La complejidad ciclomática es una métrica que mide, valga la redundancia, lo complicado que es un trozo de software.
Lo que cuenta, en la práctica, son las bifurcaciones y los bucles que tiene un programa. Se apoya en el grafo de control de flujo y sale de contar cuántos caminos independientes hay a través del código fuente.
Si el número es alto, tienes código difícil de entender, de mantener y de probar. Si es bajo, tienes código simple con el que da gusto trabajar.
La complejidad ciclomática y la calidad del código
Un código con complejidad ciclomática alta se llena de errores y de vulnerabilidades con mucha más facilidad. La razón no tiene misterio: cuanto más cuesta entender lo que hace un código, más papeletas hay de meter la pata al tocarlo.
Vamos con unos cuantos ejemplos, que se ve mejor.
Ejemplo en Python
Mala complejidad
Este fragmento hace varias cosas a la vez y con un montón de condiciones por medio. Entenderlo cuesta, y mantenerlo, más todavía.

Buena complejidad
Descomponiendo funciones en partes más pequeñas y específicas.

Partiendo la función original en trozos más pequeños y concretos, la complejidad ciclomática baja un montón. Cada función se encarga de una sola cosa, y el resultado se lee sin esfuerzo y se mantiene sin sudores.
Ejemplo en Golang
Mala complejidad
Este código va apilando condiciones anidadas, y con ellas se dispara la complejidad ciclomática. Entenderlo, probarlo y mantenerlo se vuelve un suplicio. Cada condición nueva abre un camino más por el código, con lo que crecen las opciones de que se cuele un error y de que un problema de seguridad pase desapercibido.

Buena complejidad
Después de refactorizar, la complejidad ciclomática ha bajado. He sacado parte de la lógica a funciones separadas, y así cada una es más sencilla y tiene una única responsabilidad.
Entre los switches y las funciones separadas, el código se entiende, se mantiene y se depura mucho mejor.
Y hay otra ventaja: con cada función centrada en un aspecto concreto de la evaluación de riesgos, encontrar y arreglar una vulnerabilidad deja de ser una búsqueda a ciegas.

La complejidad ciclomática y la ciberseguridad
Una complejidad ciclomática alta te puede salir cara en términos de seguridad. Estas son las tres vías por las que suele pasar:
- Los errores humanos. Cuanto más complejo es el código, más cuesta entenderlo, y más fácil es equivocarse mientras lo desarrollas o lo mantienes. De ahí salen vulnerabilidades que nadie quería meter.
- Las revisiones de código. La revisión es donde cazas buena parte de los problemas, pero con complejidad alta el revisor se pierde y la revisión pierde casi todo su valor.
- Las herramientas de test y de análisis. Las herramientas automáticas también se atragantan con el código muy complejo, y lo que no consiguen analizar bien se queda sin detectar.
La complejidad ciclomática y las fallos de seguridad
Los bugs que acaban en un fallo de seguridad casi siempre nacen de un error de programación tonto. Y cuanto más alta es la complejidad ciclomática, más probable es que ese error aparezca. Estas son las vulnerabilidades que más veces me he encontrado asociadas a código enrevesado:
- Inyecciones SQL, porque en un código complejo es fácil que la entrada del usuario acabe llegando a la consulta sin pasar por donde debería.
- Desbordamientos de búfer, cuando el manejo de datos se complica tanto que las comprobaciones de límites se quedan a medias.
- Fallos en la autenticación y la gestión de sesiones. Los sistemas de login complejos esconden fallos críticos que comprometen al usuario directamente.
- Problemas de control de acceso, con errores en la gestión de permisos que son el origen de buena parte de las escaladas de privilegios verticales.
- Inyección de código, porque un control de flujo enrevesado deja huecos por donde se cuela la entrada del usuario sin sanitizar.
- Problemas de gestión de sesiones, con tokens que no caducan cuando deben o que se reutilizan entre usuarios sin que nadie se dé cuenta.
Hay más, claro, pero estas son las que más se repiten cuando el código está pasado de vueltas.
Ejemplos de vulnerabilidades relacionadas con la complejidad ciclomática (Python)
Flujos de autenticación web
Alta complejidad ciclomática
Mira este código de autenticación. Está lleno de agujeros, pero con esta complejidad no hay quien los vea a simple vista, y mucho menos quien los arregle sin romper otra cosa.

- Este código tiene una alta complejidad ciclomática debido a múltiples condiciones y bifurcaciones anidadas.
- Utiliza MD5 para la contraseña, que no es segura.
- La validación de la contraseña se realiza después de verificar si el usuario existe y si la contraseña coincide, lo cual no es una práctica recomendada.
- La contraseña se almacena en texto plano, lo que es inseguro.
Baja complejidad ciclomática
Aquí he simplificado y refactorizado el mismo código de autenticación para bajar la complejidad ciclomática. Ahora se entiende, se prueba y se mantiene sin dolor, y con eso caen en picado las opciones de que se cuele una vulnerabilidad.

- La complejidad ciclomática se reduce significativamente debido a la eliminación de condiciones anidadas innecesarias.
- Utiliza werkzeug.security.check_password_hash para una verificación de contraseña más segura.
- Mejora la seguridad al no revelar si un nombre de usuario específico existe o no.
Ejecución de comandos
Alta complejidad ciclomática
En este ejemplo tenemos un script que hace cosas según lo que teclee el usuario, con una estructura de control complicada y una vulnerabilidad crítica dentro:

- Este código tiene una alta complejidad ciclomática debido a múltiples ramas de decisión.
- Utiliza
os.system, que es peligroso si se combina con entradas no sanitizadas, permitiendo la ejecución de comandos arbitrarios.
Baja complejidad ciclomática
Aquí hay un enfoque más seguro con una menor complejidad ciclomática.

- La complejidad ciclomática se reduce al dividir la lógica en funciones más pequeñas y claras.
- Utiliza
subprocess.run, es más seguro queos.system, y evita la ejecución insegura de comandos. - Separar las operaciones en diferentes funciones hace que el código sea más fácil de entender y mantener.
Ejemplos de vulnerabilidades relacionadas con la complejidad ciclomática (Golang)
Ejecución arbitraria de comandos
Este ejemplo en Go es una función que coge lo que escribe el usuario y con eso ejecuta comandos del sistema. La complejidad viene de las ramas condicionales que se van acumulando y de ejecutar comandos directamente a partir de esa entrada.
Alta complejidad ciclomática

- La función executeCommand procesa la entrada del usuario y ejecuta directamente comandos del sistema basados en esta entrada.
- El uso de
exec.Commandcon entrada de usuario no sanitizada introduce un riesgo significativo de seguridad, ya que puede llevar a la ejecución arbitraria de comandos. - La complejidad de manejar diferentes comandos en la misma función aumenta el riesgo de errores y hace que el código sea más difícil de auditar en busca de problemas de seguridad.
Baja complejidad ciclomática
Aquí hay un enfoque más seguro con una menor complejidad ciclomática:

- La complejidad ciclomática se reduce significativamente al dividir la función original executeCommand en funciones más pequeñas (
executeEchoylistDirectory). - Cada función es responsable de una tarea específica, lo que hace que el código sea más legible y más fácil de mantener.
- Aunque el uso de
exec.Commandsigue planteando riesgos. Separar los comandos en funciones dedicadas permite un manejo más controlado y seguro de la entrada del usuario.
Complejidad ciclomática óptima
Los valores ideales dependen del contexto y del lenguaje. Como norma general, con 10 o menos tienes código claro y fácil de seguir.
Con los años me he montado esta clasificación mirándolo desde la seguridad:
- 1-10: el rango donde quieres estar. El código se entiende y se mantiene, los tests salen solos y las opciones de meter un fallo de seguridad son bajas.
- 10-20: complejidad moderada. No es lo ideal, pero se lleva. Aquí ya necesitas tests serios y revisiones de código si quieres dormir tranquilo.
- 20-40: complejidad alta. Cuesta entender qué hace ese código y cuesta probarlo bien, con lo que suben los errores y las vulnerabilidades.
- > 40: evítalo. Ni se entiende ni se prueba, y el riesgo de errores y de agujeros de seguridad se dispara.
No te tomes estos números como dogma, son una referencia. La complejidad ciclomática hay que mirarla en el contexto concreto de ese código y de ese proyecto.
Medidas de mitigación y buenas prácticas
Casi nadie mete la complejidad ciclomática en la lista de métricas de desarrollo seguro. Y es un error, porque afecta a la seguridad de lleno. Los ejemplos de arriba lo dejan bastante claro: a más complejidad, más errores y más vulnerabilidades.
Medirla es fácil, aunque la forma de hacerlo cambia según el lenguaje y las herramientas que tengas. Lo que ya no es tan fácil es actuar sobre ella, porque a veces te obliga a cambios de calado en el código.
Medición de la complejidad ciclomática
Lo primero es medir, que para eso hay herramientas de sobra. Estas son las que yo usaría según el lenguaje:
- Python: Radon. Te calcula la complejidad ciclomática y de paso otras métricas como el índice de mantenibilidad. Flake8 también trae opciones para medirla.
- Java: Checkstyle con su módulo de complejidad ciclomática. Además de ayudarte a cumplir los estándares de codificación, te la calcula por método y por clase.
- JavaScript: ESLint. Lo conoces como linter, pero se puede configurar para que te avise de la complejidad ciclomática.
- C++: Cppcheck, una herramienta de análisis estático que mide la complejidad y de camino te caza unos cuantos tipos de errores.
- C#: Visual Studio lo trae de serie y te analiza la complejidad ciclomática sin salir del entorno.
- PHP:
- PHPMD (PHP Mess Detector) te saca los problemas de codificación de PHP, complejidad ciclomática incluida.
- PhpMetrics va más al detalle con la calidad del código PHP, y también te da la complejidad ciclomática junto a otras métricas.
- Golang:
- Gocyclo está hecho justo para eso, calcular la complejidad ciclomática de las funciones en Go.
- Go Report Card abarca bastante más, pero incluye la revisión de complejidad ciclomática dentro de su análisis.
Medidas de mitigación y mejores prácticas
Con los números en la mano, estas son las medidas que puedes aplicar para bajar la complejidad y su impacto en seguridad:
- Refactorizar el código. Revisarlo y simplificarlo de forma habitual, no cuando ya no hay quien lo toque.
- Tests. Cuantos más y más duros, mejor, sobre todo en las funciones con complejidad alta.
- Principios de diseño. SOLID y compañía, para acabar con código modular y mantenible.
- Formación. Mucha gente no ha oído hablar nunca de la complejidad ciclomática ni de lo que implica para la seguridad. Formar al equipo en esto se nota rápido.
- Revisión de código. Revisiones periódicas para pillar los problemas de complejidad mientras son pequeños.
- Análisis estático. Herramientas que te señalen solas las zonas de más riesgo.
- Pruebas de seguridad automatizadas para detectar las vulnerabilidades sin depender de que alguien se acuerde de buscarlas.
El orden depende del proyecto y de sus necesidades. Ahora bien, si solo pudiera aplicar una, refactorizaría las partes del código que tocan directamente las entradas de los usuarios, porque ahí es donde está la mayoría de las vulnerabilidades.
Conclusiones
La complejidad ciclomática sigue siendo bastante desconocida en el mundillo del desarrollo, y sin embargo tiene un efecto directo sobre la seguridad del software que sacas a producción.
Casi nunca aparece en las listas de buenas prácticas de desarrollo seguro. Debería aparecer, porque en la práctica te está diciendo dónde van a salir los problemas.
Medirla es sencillo y tienes herramientas para casi cualquier lenguaje. Actuar sobre lo que te dicen ya es otra historia, y a veces implica cambios gordos en el código. Pero incluso si no llegas a refactorizar nada, con solo medir ya sabes qué partes son las más retorcidas y, por tanto, las más propensas a esconder errores y vulnerabilidades. Y con eso ya puedes decidir por dónde empezar.