<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adriana Rafaela Laos González</title>
    <description>The latest articles on DEV Community by Adriana Rafaela Laos González (@laos19).</description>
    <link>https://dev.to/laos19</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187713%2F406deb9c-05ee-4cb6-b198-6eb40d70f9dd.jpg</url>
      <title>DEV Community: Adriana Rafaela Laos González</title>
      <link>https://dev.to/laos19</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/laos19"/>
    <language>en</language>
    <item>
      <title>CodeQL en la práctica: rastreando vulnerabilidades en una app Flask con GitHub Actions y Render</title>
      <dc:creator>Adriana Rafaela Laos González</dc:creator>
      <pubDate>Fri, 16 Oct 2026 02:41:33 +0000</pubDate>
      <link>https://dev.to/laos19/codeql-en-la-practica-rastreando-vulnerabilidades-en-una-app-flask-con-github-actions-y-render-5c0p</link>
      <guid>https://dev.to/laos19/codeql-en-la-practica-rastreando-vulnerabilidades-en-una-app-flask-con-github-actions-y-render-5c0p</guid>
      <description>&lt;h1&gt;
  
  
  CodeQL en la práctica: rastreando vulnerabilidades en una app Flask con GitHub Actions y Render
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Autora:&lt;/strong&gt; Adriana Laos González&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Curso:&lt;/strong&gt; Calidad y Pruebas de Software (SI784)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Institución:&lt;/strong&gt; Universidad Privada de Tacna&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Repositorio:&lt;/strong&gt; &lt;a href="https://github.com/Laos19/sast-flask-demo" rel="noopener noreferrer"&gt;https://github.com/Laos19/sast-flask-demo&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Aplicación desplegada:&lt;/strong&gt; &lt;a href="https://sast-flask-demo.onrender.com" rel="noopener noreferrer"&gt;https://sast-flask-demo.onrender.com&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Artículo complementario (Bandit):&lt;/strong&gt; [Agregar enlace]&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Video (máximo 5 minutos):&lt;/strong&gt; [Agregar enlace]&lt;/p&gt;
&lt;h2&gt;
  
  
  1. ¿Por qué otra herramienta SAST?
&lt;/h2&gt;

&lt;p&gt;En los laboratorios del curso ya usamos SonarCloud, Snyk y Semgrep. Para este trabajo buscamos en la lista &lt;em&gt;Source Code Analysis Tools&lt;/em&gt; de OWASP y en la de NIST herramientas que no hubiéramos usado. Mi compañero eligió Bandit y yo elegí CodeQL, que en la lista de OWASP aparece dentro de la entrada GitHub Advanced Security.&lt;/p&gt;

&lt;p&gt;La primera herramienta que tenía asignada era Bearer CLI. La descarté porque sobre nuestra aplicación solo detectó 1 de 5 vulnerabilidades. Revisando sus reglas oficiales (&lt;code&gt;Bearer/bearer-rules&lt;/code&gt;), vi que, en Python, sus fuentes de “entrada externa” incluyen Django, &lt;code&gt;input()&lt;/code&gt;, &lt;code&gt;sys.argv&lt;/code&gt; y otras, pero no &lt;code&gt;flask.request&lt;/code&gt;. Por eso no detecta las inyecciones en una aplicación Flask. La lección es que, antes de adoptar una herramienta SAST, hay que comprobar que entiende el framework que utiliza el proyecto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAST&lt;/strong&gt; (&lt;em&gt;Static Application Security Testing&lt;/em&gt;) significa analizar el código fuente sin ejecutarlo para encontrar patrones o flujos inseguros antes de que lleguen a producción.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. ¿Qué es CodeQL?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mantenedor:&lt;/strong&gt; GitHub (Microsoft); procede de Semmle, adquirida en 2019.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Técnica:&lt;/strong&gt; convierte el código en una base de datos que se consulta mediante el lenguaje QL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Característica diferencial:&lt;/strong&gt; utiliza &lt;em&gt;taint tracking&lt;/em&gt; para seguir un dato desde la fuente —por ejemplo, &lt;code&gt;request.args&lt;/code&gt;— hasta un destino peligroso, como &lt;code&gt;execute()&lt;/code&gt; o &lt;code&gt;subprocess.run()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lenguajes:&lt;/strong&gt; C/C++, C#, Go, Java/Kotlin, JavaScript/TypeScript, Python, Ruby, Swift, entre otros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licencia y disponibilidad:&lt;/strong&gt; la CLI es gratuita para código abierto e investigación académica. Las consultas están disponibles en el repositorio &lt;a href="https://github.com/github/codeql" rel="noopener noreferrer"&gt;github/codeql&lt;/a&gt;. Los repositorios privados comerciales requieren GitHub Advanced Security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una herramienta basada en patrones puede ver una línea como &lt;code&gt;execute(f"... {q} ...")&lt;/code&gt; y avisar porque “parece SQL armado con f-string”. CodeQL, en cambio, puede demostrar que &lt;code&gt;q&lt;/code&gt; proviene del usuario y llega sin sanitizar a &lt;code&gt;execute()&lt;/code&gt;. Así se reducen los falsos positivos y cada hallazgo incluye una explicación.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. La aplicación de prueba: NotasApp
&lt;/h2&gt;

&lt;p&gt;Creamos una aplicación pequeña con Python 3.12, Flask, SQLite y pytest. Tiene registro e inicio de sesión, notas personales, un buscador y una utilidad para hacer &lt;em&gt;ping&lt;/em&gt; a un host. Introdujimos cinco vulnerabilidades de forma intencional:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Vulnerabilidad&lt;/th&gt;
&lt;th&gt;Ubicación&lt;/th&gt;
&lt;th&gt;CWE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;V1&lt;/td&gt;
&lt;td&gt;SQL Injection mediante f-string&lt;/td&gt;
&lt;td&gt;&lt;code&gt;buscar()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CWE-89&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V2&lt;/td&gt;
&lt;td&gt;Command Injection mediante &lt;code&gt;shell=True&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ping()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CWE-78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V3&lt;/td&gt;
&lt;td&gt;Secreto escrito directamente en el código (&lt;code&gt;SECRET_KEY = "super-secreto-123"&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CWE-798&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V4&lt;/td&gt;
&lt;td&gt;Uso de MD5 para contraseñas&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hash_password()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CWE-327&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.run(debug=True)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Arranque de la aplicación&lt;/td&gt;
&lt;td&gt;CWE-489&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Las comprobamos localmente antes de escanear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;V1:&lt;/strong&gt; al buscar &lt;code&gt;' OR 1=1 --&lt;/code&gt;, el usuario &lt;code&gt;ana&lt;/code&gt; puede ver la nota privada de &lt;code&gt;beto&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V2:&lt;/strong&gt; al hacer &lt;em&gt;ping&lt;/em&gt; a &lt;code&gt;127.0.0.1 &amp;amp;&amp;amp; whoami&lt;/code&gt;, el servidor ejecuta &lt;code&gt;whoami&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El repositorio contiene dos etiquetas (&lt;em&gt;tags&lt;/em&gt;): &lt;code&gt;v1-vulnerable&lt;/code&gt; y &lt;code&gt;v2-corregido&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Usar CodeQL en local
&lt;/h2&gt;

&lt;p&gt;Descargamos el paquete oficial de CodeQL CLI y sus consultas. El paquete comprimido ocupa alrededor de 700 MB.&lt;/p&gt;
&lt;h3&gt;
  
  
  Paso 1. Descargar y descomprimir el paquete
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; codeql-bundle.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.27.1/codeql-bundle-win64.tar.gz

&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; codeql-bundle.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Paso 2. Crear la base de datos del código
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codeql database create codeql-db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;python &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--codescanning-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.github/codeql/codeql-config.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Paso 3. Ejecutar las consultas y exportar el resultado SARIF
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codeql database analyze codeql-db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sarif-latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codeql.sarif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Con la suite predeterminada, CodeQL detectó cuatro de las cinco vulnerabilidades: no encontró el secreto escrito en el código (V3). Para detectarlo, activamos una consulta experimental en &lt;code&gt;.github/codeql/codeql-config.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CodeQL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NotasApp"&lt;/span&gt;
&lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-extended&lt;/span&gt;
&lt;span class="na"&gt;packs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;codeql/python-queries:experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resultados sobre la versión vulnerable: 5 de 5
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regla&lt;/th&gt;
&lt;th&gt;Severidad&lt;/th&gt;
&lt;th&gt;Línea&lt;/th&gt;
&lt;th&gt;Vulnerabilidad&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;py/command-line-injection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.8 — Critical&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;V2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;py/sql-injection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8.8 — High&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;td&gt;V1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;py/flask-constant-secret-key&lt;/code&gt; (experimental)&lt;/td&gt;
&lt;td&gt;8.5 — High&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;V3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;py/weak-sensitive-data-hashing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.5 — High&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;V4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;py/flask-debug&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.5 — High&lt;/td&gt;
&lt;td&gt;136&lt;/td&gt;
&lt;td&gt;V5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Todos los hallazgos aparecieron con precisión alta y ninguno fue un falso positivo. Para V1, CodeQL muestra el recorrido completo del dato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request.args.get("q") (línea 110)
    → q
    → consulta (línea 114)
    → execute(consulta) (línea 115)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;También distingue que MD5 se aplica a una contraseña y no a cualquier dato. Por eso la regla se llama &lt;code&gt;weak-sensitive-data-hashing&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Automatización: GitHub Actions + quality gate
&lt;/h2&gt;

&lt;p&gt;El pipeline &lt;code&gt;.github/workflows/ci-sast-deploy.yml&lt;/code&gt; contiene cuatro trabajos (&lt;em&gt;jobs&lt;/em&gt;): &lt;code&gt;tests&lt;/code&gt; (pytest), &lt;code&gt;sast-bandit&lt;/code&gt;, &lt;code&gt;sast-codeql&lt;/code&gt; y &lt;code&gt;deploy&lt;/code&gt;. El trabajo de CodeQL es el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;sast-codeql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;# Para publicar en Code Scanning&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/init@v4&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
        &lt;span class="na"&gt;config-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/codeql/codeql-config.yml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/analyze@v4&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codeql-python&lt;/span&gt;
        &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codeql-sarif&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Quality gate CodeQL (severidad alta)&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3 scripts/sarif_gate.py codeql-sarif/python.sarif &lt;/span&gt;&lt;span class="m"&gt;7.0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reporte-codeql&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codeql-sarif/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En GitHub Actions no hace falta instalar CodeQL manualmente, porque &lt;code&gt;codeql-action/init&lt;/code&gt; lo descarga automáticamente. Los resultados aparecen en &lt;strong&gt;Security → Code scanning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Un detalle importante: la acción oficial de CodeQL no hace fallar el pipeline solo por encontrar vulnerabilidades; únicamente las reporta. Para establecer un &lt;em&gt;quality gate&lt;/em&gt; real, escribimos un script (&lt;code&gt;scripts/sarif_gate.py&lt;/code&gt;) que lee el archivo SARIF y termina con código de salida &lt;code&gt;1&lt;/code&gt; si encuentra algún hallazgo con &lt;code&gt;security-severity &amp;gt;= 7.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El despliegue solo ocurre si los tres trabajos anteriores pasan y el evento es un &lt;em&gt;push&lt;/em&gt; a &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sast-bandit&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sast-codeql&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main' &amp;amp;&amp;amp; github.event_name != 'pull_request'&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -fsS -X POST "${{ secrets.RENDER_DEPLOY_HOOK }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con los &lt;em&gt;gates&lt;/em&gt; activos, los tiempos de ejecución observados fueron: pruebas, 9 segundos; Bandit, 18 segundos; CodeQL, 39 segundos; y despliegue, 3 segundos.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Corrección: de 5 hallazgos a 0
&lt;/h2&gt;

&lt;p&gt;Hicimos un &lt;em&gt;commit&lt;/em&gt; por cada vulnerabilidad.&lt;/p&gt;

&lt;h3&gt;
  
  
  V1. SQL Injection: consulta parametrizada
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id, titulo, contenido FROM notas WHERE usuario_id = ? AND titulo LIKE ?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usuario_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  V2. Command Injection: evitar el shell y validar el host
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;host_valido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ejecutable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opcion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se utiliza una lista de argumentos en lugar de &lt;code&gt;shell=True&lt;/code&gt;, y el host se valida con &lt;code&gt;ipaddress&lt;/code&gt; y una expresión regular.&lt;/p&gt;

&lt;h3&gt;
  
  
  V3. Secreto en el código: variable de entorno
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  V4. Contraseñas: sustituir MD5 por scrypt de Werkzeug
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;generate_password_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;check_password_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  V5. Modo debug: desactivado por defecto
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FLASK_DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después de las correcciones, los resultados fueron:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severidad&lt;/th&gt;
&lt;th&gt;Antes&lt;/th&gt;
&lt;th&gt;Después&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tras corregir, no quedaron alertas de CodeQL. La herramienta entiende que, en &lt;code&gt;subprocess.run([...])&lt;/code&gt;, el host es un argumento y no un comando. Bandit, en cambio, sigue mostrando allí una advertencia informativa (B603). Además, aumentamos las pruebas de pytest de cuatro a siete, incorporando pruebas de regresión para V1, V2 y V4.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Probando el quality gate
&lt;/h2&gt;

&lt;p&gt;En la rama &lt;code&gt;demo-gate&lt;/code&gt; volvimos a introducir V2 (&lt;code&gt;shell=True&lt;/code&gt;) y abrimos el PR #1. El resultado fue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tests&lt;/code&gt;: falló la prueba de regresión de V2.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sast-bandit&lt;/code&gt;: falló por la regla B602, severidad alta.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sast-codeql&lt;/code&gt;: falló por &lt;code&gt;py/command-line-injection&lt;/code&gt;, con severidad 9.8.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy&lt;/code&gt;: omitido; no se desplegó la aplicación.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En el pull request, CodeQL comenta la línea afectada y propone un &lt;em&gt;Suggested changeset&lt;/em&gt; (autocorrección). Esta prueba permitió comprobar que la vulnerabilidad reintroducida queda bloqueada antes de llegar a producción.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Despliegue en Render (nube pública, plan gratuito)
&lt;/h2&gt;

&lt;p&gt;Para desplegar la aplicación:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;En &lt;a href="https://render.com" rel="noopener noreferrer"&gt;render.com&lt;/a&gt;, crear &lt;strong&gt;New → Web Service&lt;/strong&gt; y seleccionar el repositorio &lt;code&gt;Laos19/sast-flask-demo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configurar el comando de construcción como &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; y el comando de inicio como &lt;code&gt;gunicorn app.app:app&lt;/code&gt;. Seleccionar una instancia gratuita.&lt;/li&gt;
&lt;li&gt;Configurar las variables de entorno &lt;code&gt;SECRET_KEY&lt;/code&gt; —generada desde Render— y &lt;code&gt;FLASK_DEBUG=0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Desactivar &lt;strong&gt;Auto-Deploy&lt;/strong&gt;, copiar el &lt;em&gt;Deploy Hook&lt;/em&gt; y guardarlo en GitHub como el secreto &lt;code&gt;RENDER_DEPLOY_HOOK&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Por seguridad, solo desplegamos la versión corregida. Publicar &lt;code&gt;v1-vulnerable&lt;/code&gt; habría dejado una ejecución remota de comandos expuesta en internet. Un detalle observado fue que Render tiene a Cloudflare delante y su WAF devolvió &lt;code&gt;403 Blocked&lt;/code&gt; al &lt;em&gt;payload&lt;/em&gt; &lt;code&gt;' OR 1=1 --&lt;/code&gt;. Esto constituye defensa en profundidad, pero no reemplaza la corrección del código.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Problemas que encontramos
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problema&lt;/th&gt;
&lt;th&gt;Solución&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bearer CLI no reconoce &lt;code&gt;flask.request&lt;/code&gt; y detecta solo 1 de 5 vulnerabilidades.&lt;/td&gt;
&lt;td&gt;Cambiar a CodeQL después de revisar sus reglas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeQL no detecta la clave &lt;code&gt;SECRET_KEY&lt;/code&gt; fija con la suite estándar.&lt;/td&gt;
&lt;td&gt;Activar la consulta experimental &lt;code&gt;py/flask-constant-secret-key&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;La acción de CodeQL no bloquea el pipeline ante hallazgos.&lt;/td&gt;
&lt;td&gt;Añadir &lt;code&gt;sarif_gate.py&lt;/code&gt; con umbral 7.0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render muestra &lt;code&gt;Failed to find attribute 'app' in 'app'&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Usar &lt;code&gt;gunicorn app.app:app&lt;/code&gt; como comando de inicio.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;El paquete de CodeQL es pesado (aproximadamente 1,5 GB descomprimido).&lt;/td&gt;
&lt;td&gt;En CI no hace falta instalarlo manualmente: la acción oficial lo descarga.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  10. Conclusiones
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ventajas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Realiza análisis del flujo de datos.&lt;/li&gt;
&lt;li&gt;No produjo falsos positivos en los hallazgos observados y no dejó alertas después de las correcciones.&lt;/li&gt;
&lt;li&gt;Se integra de forma nativa con GitHub mediante Code Scanning, comentarios en pull requests y sugerencias de autocorrección.&lt;/li&gt;
&lt;li&gt;Soporta numerosos lenguajes de programación.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitaciones
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Es pesado: en las pruebas locales tardó unas 50 veces más que Bandit.&lt;/li&gt;
&lt;li&gt;La detección de la clave fija en el código requirió una consulta experimental.&lt;/li&gt;
&lt;li&gt;Para implementar un &lt;em&gt;quality gate&lt;/em&gt; fue necesario crear un script propio.&lt;/li&gt;
&lt;li&gt;La licencia gratuita está limitada a código abierto o investigación académica; los repositorios privados comerciales requieren GitHub Advanced Security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recomendamos combinar ambas herramientas: &lt;strong&gt;Bandit como filtro rápido en cada commit y CodeQL para obtener hallazgos más precisos y explicados&lt;/strong&gt;. En nuestro pull request de prueba, las dos herramientas bloquearon la vulnerabilidad reintroducida antes de que llegara a producción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;OWASP Foundation. &lt;em&gt;Source Code Analysis Tools&lt;/em&gt;. &lt;a href="https://owasp.org/www-community/Source_Code_Analysis_Tools" rel="noopener noreferrer"&gt;https://owasp.org/www-community/Source_Code_Analysis_Tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NIST. &lt;em&gt;Source Code Security Analyzers&lt;/em&gt;. &lt;a href="https://www.nist.gov/itl/csd/secure-systems-and-applications/source-code-security-analyzers" rel="noopener noreferrer"&gt;https://www.nist.gov/itl/csd/secure-systems-and-applications/source-code-security-analyzers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyCQA. &lt;em&gt;Bandit documentation&lt;/em&gt;. &lt;a href="https://bandit.readthedocs.io/" rel="noopener noreferrer"&gt;https://bandit.readthedocs.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub. &lt;em&gt;CodeQL documentation&lt;/em&gt;. &lt;a href="https://codeql.github.com/docs/" rel="noopener noreferrer"&gt;https://codeql.github.com/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub. &lt;em&gt;About code scanning (SARIF)&lt;/em&gt;. &lt;a href="https://docs.github.com/en/code-security/code-scanning" rel="noopener noreferrer"&gt;https://docs.github.com/en/code-security/code-scanning&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Render. &lt;em&gt;Deploy Hooks&lt;/em&gt;. &lt;a href="https://render.com/docs/deploy-hooks" rel="noopener noreferrer"&gt;https://render.com/docs/deploy-hooks&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Etiquetas sugeridas para DEV Community:&lt;/strong&gt; &lt;code&gt;security&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;github&lt;/code&gt;, &lt;code&gt;webdev&lt;/code&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>python</category>
      <category>security</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
