<?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: Victor Cesar Carretero Hualpa</title>
    <description>The latest articles on DEV Community by Victor Cesar Carretero Hualpa (@viccarretero).</description>
    <link>https://dev.to/viccarretero</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%2F4160804%2F1f7d8363-3abb-4175-8320-31203d4e64e7.png</url>
      <title>DEV Community: Victor Cesar Carretero Hualpa</title>
      <link>https://dev.to/viccarretero</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viccarretero"/>
    <language>en</language>
    <item>
      <title>467 hallazgos de Bandit: análisis de seguridad con GitHub Actions y despliegue en Azure</title>
      <dc:creator>Victor Cesar Carretero Hualpa</dc:creator>
      <pubDate>Sun, 04 Oct 2026 05:33:17 +0000</pubDate>
      <link>https://dev.to/viccarretero/467-hallazgos-de-bandit-analisis-de-seguridad-con-github-actions-y-despliegue-en-azure-38l6</link>
      <guid>https://dev.to/viccarretero/467-hallazgos-de-bandit-analisis-de-seguridad-con-github-actions-y-despliegue-en-azure-38l6</guid>
      <description>&lt;p&gt;&lt;em&gt;Un pipeline de GitHub Actions que escanea con SAST antes de cada despliegue, una app FastAPI + Oracle y una lección sobre cómo separar el ruido de la señal.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;La primera vez que abrimos bandit-report.json nos llevamos un susto: &lt;strong&gt;467 hallazgos&lt;/strong&gt; en un proyecto de unas 3 900 líneas. Nos tomó un rato entender que ese número, por sí solo, decía muy poco. Este artículo cuenta cómo llegamos ahí, integrando Bandit en un pipeline de CI/CD, y sobre todo cómo aprendimos a interpretar lo que nos devolvió.&lt;/p&gt;

&lt;h3&gt;
  
  
  El proyecto en dos minutos
&lt;/h3&gt;

&lt;p&gt;Somos dos estudiantes de Ingeniería de Sistemas de la Universidad Privada de Tacna. Para el curso de Calidad y Pruebas de Software desarrollamos un &lt;strong&gt;Framework de Pruebas de Base de Datos SQL&lt;/strong&gt; (un prototipo académico, sin cliente real): una aplicación web hecha con &lt;strong&gt;FastAPI&lt;/strong&gt; y orientada a &lt;strong&gt;Oracle&lt;/strong&gt;, en la que un analista QA registra una consulta, indica qué espera obtener, la ejecuta y revisa después qué ocurrió.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Función&lt;/th&gt;
&lt;th&gt;Cómo funciona&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Perfiles de conexión&lt;/td&gt;
&lt;td&gt;Guardan host, puerto, usuario y service name. La contraseña nunca se persiste: se pide al probar o ejecutar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Casos de prueba&lt;/td&gt;
&lt;td&gt;Una sentencia SQL, un resultado esperado y un tipo de validación (ROW_COUNT o EXISTS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suites&lt;/td&gt;
&lt;td&gt;Conjuntos de casos del mismo proyecto que se ejecutan juntos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Veredicto&lt;/td&gt;
&lt;td&gt;PASS, FAIL o ERROR por cada ejecución&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historial y dashboard&lt;/td&gt;
&lt;td&gt;Registro consultable de cada corrida, con filtros por proyecto, suite, caso y estado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Como la herramienta ejecuta SQL de verdad, le pusimos controles propios: una sola sentencia por caso, DDL y TCL bloqueados, ROLLBACK obligatorio después de todo DML y reglas distintas por ambiente (TEST; STAGING, que pide confirmación; y PRODUCTION, solo lectura y con las filas ocultas). La autenticación usa una cookie HttpOnly con un token firmado.&lt;/p&gt;

&lt;p&gt;Nada de esto nos exime de revisar nuestro propio código. Un ROLLBACK no cubre todos los casos (Oracle hace &lt;em&gt;commit&lt;/em&gt; implícito con DDL y permite transacciones autónomas) y sqlparse, que usamos para reconocer sentencias, no pretende ser un validador. Por eso sumamos otra capa.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Por qué SAST?
&lt;/h3&gt;

&lt;p&gt;Una forma sencilla de verlo: SAST (&lt;em&gt;Static Application Security Testing&lt;/em&gt;) es un revisor de código que no se cansa y que lee cada archivo sin necesidad de ejecutar la aplicación. Encuentra cierto tipo de errores, como credenciales escritas en el código, excepciones que se tragan los fallos o llamadas a procesos del sistema, mientras todavía es barato corregirlos.&lt;/p&gt;

&lt;p&gt;Para Python elegimos &lt;strong&gt;Bandit&lt;/strong&gt;. Analiza el árbol sintáctico (AST) de cada archivo, aplica reglas con identificadores como B105 o B110, relaciona cada hallazgo con un CWE y genera un reporte JSON. OWASP lo incluye en su catálogo de herramientas de análisis de código fuente, aunque aclara que no respalda ninguna en particular. También tiene límites claros: detecta patrones, no entiende la lógica de negocio y no mira las dependencias.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arquitectura del despliegue
&lt;/h2&gt;

&lt;p&gt;Podemos pensar el despliegue en tres capas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Código, en GitHub.&lt;/strong&gt; El repositorio es público: &lt;a href="https://github.com/Geraldzvallos/framework-pruebas-sql-publico" rel="noopener noreferrer"&gt;https://github.com/Geraldzvallos/framework-pruebas-sql-publico&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatización, en GitHub Actions.&lt;/strong&gt; Cada push a la rama main ejecuta el escáner de seguridad y, solo si pasa, actualiza el despliegue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ejecución, en Microsoft Azure.&lt;/strong&gt; Una máquina virtual Linux con Docker Compose que aloja:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt;, que termina HTTPS y expone los puertos 80 y 443.&lt;/li&gt;
&lt;li&gt;Un contenedor con &lt;strong&gt;FastAPI&lt;/strong&gt;, que sirve la API REST y la interfaz web.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; con migraciones de &lt;strong&gt;Alembic&lt;/strong&gt; para los metadatos y el historial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oracle Free&lt;/strong&gt; como base objetivo de la demostración. La API y Oracle se hablan por la red interna de los contenedores.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;La aplicación está en línea aquí: &lt;a href="https://frameworksql.sytes.net/login" rel="noopener noreferrer"&gt;https://frameworksql.sytes.net/login&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┐   push    ┌──────────────────────────────┐   SSH    ┌───────────────────────────┐
│  GitHub  │ ───────▶  │  GitHub Actions              │ ───────▶ │  VM de Azure (Docker)     │
│  (main)  │           │  1) Bandit  2) ¿pasa? 3) CD  │          │  Caddy → FastAPI → Oracle │
└──────────┘           └──────────────────────────────┘          └───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una precisión importante: Oracle Free sirve para demostrar, pero el fabricante no ofrece soporte ni parches para esa edición. En un entorno empresarial usaríamos una edición soportada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementación de la automatización
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Primero, a mano
&lt;/h3&gt;

&lt;p&gt;Antes de tocar el pipeline probamos Bandit en nuestra máquina:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;bandit
bandit &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; json &lt;span class="nt"&gt;-o&lt;/span&gt; bandit-report.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queríamos la salida en JSON porque es un artefacto que se puede guardar, comparar entre ejecuciones y procesar con un script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Después, el job de seguridad
&lt;/h3&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="s"&gt;Security Scan and Deploy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;security-scan&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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&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/setup-python@v5&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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.12'&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;Install Bandit (SAST Tool)&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;pip install bandit&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;Run Security Scan&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;bandit -r ./framework-pruebas-sql -f json -o bandit-report.json || &lt;/span&gt;&lt;span class="kc"&gt;true&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;Upload Security Report&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;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;sast-report&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;bandit-report.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  La decisión más útil aquí fue enfocarnos en generar el artefacto. Con la acción upload-artifact, GitHub guarda el bandit-report.json de cada ejecución. Así tenemos la evidencia descargable de las vulnerabilidades lista para ser procesada
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Un ajuste que nos habría ahorrado ruido
&lt;/h3&gt;

&lt;p&gt;Nuestro escaneo apuntó a la raíz del repositorio, así que también analizó tests/. Es posible fijar el alcance desde pyproject.toml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.bandit]&lt;/span&gt;
&lt;span class="py"&gt;exclude_dirs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'tests'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s1"&gt;'bandit[toml]'&lt;/span&gt;
bandit &lt;span class="nt"&gt;-c&lt;/span&gt; pyproject.toml &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resultados del escaneo
&lt;/h2&gt;

&lt;p&gt;El reporte, generado por nuestro pipeline el 3 de octubre de 2026 con Bandit 1.9.4, cubrió &lt;strong&gt;3 936 líneas de código&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total:&lt;/strong&gt; 467 hallazgos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severidad:&lt;/strong&gt; 0 altos, 1 medio y 466 bajos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confianza:&lt;/strong&gt; 427 altos y 40 medios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La lista por regla:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;B101 (assert_used): &lt;strong&gt;410&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B105 (hardcoded_password_string): &lt;strong&gt;25&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B106 (hardcoded_password_funcarg): &lt;strong&gt;15&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B110 (try_except_pass): &lt;strong&gt;5&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B603 (subprocess_without_shell_equals_true): &lt;strong&gt;5&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B607 (start_process_with_partial_path): &lt;strong&gt;4&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B404 (import de subprocess): &lt;strong&gt;2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;B310 (urllib.urlopen): &lt;strong&gt;1&lt;/strong&gt;, el único de severidad media&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Leer el reporte en tres pasos
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Severidad.&lt;/strong&gt; Ninguno es alto. Ya con eso se desinfla el susto inicial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ubicación.&lt;/strong&gt; De los 467 hallazgos, &lt;strong&gt;461 (98,7 %) están en tests/&lt;/strong&gt;. Solo 6 afectan a código que no es de pruebas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contexto.&lt;/strong&gt; Un mismo patrón puede ser grave en producción e irrelevante en un test. Bandit no sabe distinguirlos; nosotros sí.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Los que sí tocan código que no es de pruebas
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;B110: excepciones silenciadas (5 casos).&lt;/strong&gt; Dos están en el motor de ejecución, app/engine/executor.py, y tres en la migración 001 de Alembic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El riesgo en producción es la invisibilidad. Si cerrar una conexión o un cursor falla de forma recurrente, nadie se entera hasta que se agotan los recursos, y el síntoma aparece lejos de su causa. En la migración, un drop_constraint que falla sin avisar puede dejar el esquema a medias. Lo corregiríamos capturando el error específico del driver y registrándolo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;oracledb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No se pudo cerrar la conexión&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;B310: urlopen sin validar el esquema (1 caso, severidad media).&lt;/strong&gt; Aparece en scripts/validar_flujo_oracle.py. La función urlopen también acepta esquemas como file://, de modo que, si una URL llegara a estar bajo control de un tercero, podría leerse un archivo local (CWE-22). En un script interno el riesgo real es bajo, pero validar el esquema es barato:&lt;br&gt;
&lt;/p&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;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http&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;https&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Solo se permiten URLs http o https&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;
  
  
  Los que parecen graves, pero aquí no lo son
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;B105 y B106: contraseñas en el código (40 casos).&lt;/strong&gt; Son los más llamativos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TargetDatabaseExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost/xe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;secret_password&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;Una credencial real en el código queda para siempre en el historial de Git y, en un repositorio público, a la vista de cualquiera (CWE-259). Es de las fallas más frecuentes y más fáciles de aprovechar. En nuestro caso, los 40 hallazgos están en archivos de prueba y son valores ficticios, como secret_password o super_secret_password_999. Aun así, vale la pena ordenarlos (constantes, &lt;em&gt;fixtures&lt;/em&gt; y un # nosec B105 con justificación), porque en un reporte con tantos falsos positivos el día que aparezca una credencial real será fácil pasarla por alto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B603, B607 y B404: procesos del sistema (11 casos).&lt;/strong&gt; Los tests invocan Alembic con subprocess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alembic&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;upgrade&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;head&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B603 pide comprobar que ninguna entrada no confiable llegue al comando (CWE-78) y B607 advierte que el ejecutable se resuelve por nombre, de modo que alguien que controle el PATH podría colar otro programa. Aquí los argumentos son constantes y no hay shell=True, por lo que el riesgo es bajo. Aun así, resolvemos ambas alertas fijando el intérprete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&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="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-m&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;alembic&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;upgrade&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;head&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;B101: 410 assert (el ruido puro).&lt;/strong&gt; Bandit recuerda que los assert se eliminan al ejecutar Python con -O. Sería un problema si protegieran una regla de seguridad; en pytest son la forma normal de comprobar. Son el 88 % del reporte y no hay nada que accionar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión y aprendizajes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Un número grande no es un riesgo grande.&lt;/strong&gt; Cero hallazgos altos y seis fuera de tests/ cuentan una historia muy distinta a la del titular de 467.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El contexto es parte del análisis.&lt;/strong&gt; Bandit detecta patrones; saber si el patrón vive en un test o en el motor de ejecución es trabajo humano.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Una puerta que bloquea todo se desactiva.&lt;/strong&gt; Bloquear por severidad alta e informar el resto mantuvo el pipeline útil y respetado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El alcance se diseña.&lt;/strong&gt; Excluir tests/ de la puerta, o escanearlos aparte de forma informativa, habría reducido el reporte de 467 a 6 hallazgos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAST es una capa más.&lt;/strong&gt; No sustituye la auditoría de dependencias (por ejemplo, pip-audit), las pruebas negativas de nuestra política SQL ni los permisos mínimos en Oracle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El siguiente paso para nosotros es corregir los B110 y el B310, fijar el alcance en pyproject.toml y sumar la revisión de dependencias al mismo pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video demostrativo
&lt;/h2&gt;

&lt;p&gt;Puedes ver el flujo completo, desde el push hasta el despliegue automático, en este video:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/hDyOvQJL2pE" rel="noopener noreferrer"&gt;https://youtu.be/hDyOvQJL2pE&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aplicación: &lt;a href="https://frameworksql.sytes.net/login" rel="noopener noreferrer"&gt;https://frameworksql.sytes.net/login&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repositorio: &lt;a href="https://github.com/Geraldzvallos/framework-pruebas-sql-publico" rel="noopener noreferrer"&gt;https://github.com/Geraldzvallos/framework-pruebas-sql-publico&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>githubactions</category>
      <category>azure</category>
    </item>
  </channel>
</rss>
