<?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: Nicole Rios</title>
    <description>The latest articles on DEV Community by Nicole Rios (@korins707).</description>
    <link>https://dev.to/korins707</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%2F4160803%2Fe64a35bb-fd69-4cd7-8ff1-25bb6acca07b.jpg</url>
      <title>DEV Community: Nicole Rios</title>
      <link>https://dev.to/korins707</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/korins707"/>
    <language>en</language>
    <item>
      <title>Por qué no usamos MS SQL Server: seis estrategias de respaldo que sí se pueden verificar</title>
      <dc:creator>Nicole Rios</dc:creator>
      <pubDate>Sun, 04 Oct 2026 04:28:41 +0000</pubDate>
      <link>https://dev.to/korins707/por-que-no-usamos-ms-sql-server-seis-estrategias-de-respaldo-que-si-se-pueden-verificar-gld</link>
      <guid>https://dev.to/korins707/por-que-no-usamos-ms-sql-server-seis-estrategias-de-respaldo-que-si-se-pueden-verificar-gld</guid>
      <description>&lt;h1&gt;
  
  
  Por qué no usamos MS SQL Server: seis estrategias de respaldo que sí se pueden verificar
&lt;/h1&gt;

&lt;p&gt;El enunciado pedía estrategias de copia de seguridad &lt;strong&gt;excluyendo MS SQL Server&lt;/strong&gt;.&lt;br&gt;
En nuestro equipo la exclusión seßeumplió en el código, no solo en el texto: la API&lt;br&gt;
&lt;strong&gt;rechaza&lt;/strong&gt; cualquier motor que no sea MongoDB, Redis o Neo4j, y hay un caso de&lt;br&gt;
prueba que lo comprueba.&lt;/p&gt;

&lt;p&gt;Pero dejar SQL Server fuera es la parte fácil. Lo difícil resultó ser la otra&lt;br&gt;
pregunta: &lt;strong&gt;¿qué estrategia de respaldo puedes verificar de verdad?&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Los enlaces del proyecto
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repositorio público con el código, la suite y el índice de respaldos:&lt;/strong&gt;
&lt;a href="https://github.com/dejameingresar/dataforge-backup-nosql" rel="noopener noreferrer"&gt;https://github.com/dejameingresar/dataforge-backup-nosql&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aplicación desplegada en la nube (Render, creada desde &lt;code&gt;render.yaml&lt;/code&gt;):&lt;/strong&gt;
&lt;a href="https://dataforge-backup-si783.onrender.com" rel="noopener noreferrer"&gt;https://dataforge-backup-si783.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artículo complementario del equipo:&lt;/strong&gt; &lt;a href="https://dev.to/dejameingresar/seis-estrategias-de-respaldo-para-bases-de-datos-nosql-y-que-falla-de-verdad-5hce"&gt;&lt;em&gt;Seis estrategias de respaldo para
bases de datos NoSQL, y qué falla de verdad&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  La exclusión, aplicada de verdad
&lt;/h2&gt;

&lt;p&gt;SQL Server no aparece en ninguna decisión de diseño del proyecto. Aparece en dos&lt;br&gt;
lugares, y en los dos para rechazarlo:&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="c1"&gt;# app/tests/casos.py — el motor prohibido tiene que dar 400, no 500
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;motor&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="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres&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;MONGODB&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;mssql&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;# smoke.py — "un motor prohibido (MS SQL Server) se rechaza con 400"
&lt;/span&gt;&lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cuerpo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;post&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;api/respaldar/mssql/completo&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;Eso no es simbólico. Significa que la API tiene una noción de "motor no&lt;br&gt;
soportado" separada de "motor caído", y que un error de usuario siempre devuelve&lt;br&gt;
400 en lugar de romper el servidor.&lt;/p&gt;
&lt;h2&gt;
  
  
  Por qué no MS SQL Server: cuatro razones que se comprobaron
&lt;/h2&gt;

&lt;p&gt;No lo excluimos por gusto ni por mods. Lo excluimos porque su modelo de respaldo&lt;br&gt;
no encaja con lo que este proyecto tenía que demostrar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. El respaldo de SQL Server depende de una instancia viva, y su artefacto&lt;br&gt;
depende de la versión que lo creó.&lt;/strong&gt; Un &lt;code&gt;.bak&lt;/code&gt; se genera con &lt;code&gt;BACKUP DATABASE&lt;/code&gt; y&lt;br&gt;
se restaura con &lt;code&gt;RESTORE&lt;/code&gt;, pero el archivo lleva la versión del motor que lo&lt;br&gt;
produjo: restaurarlo exige una edición compatible. En un proyecto cuyo objetivo&lt;br&gt;
es que el respaldo sobreviva a la caída del motor, depender de que el motor&lt;br&gt;
volvió con la misma edición es una restricción que no se puede verificar desde&lt;br&gt;
fuera.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Su automatización exige más superficie que un solo binario.&lt;/strong&gt; Un respaldo&lt;br&gt;
programado de SQL Server necesita la instancia, las credenciales, un destino&lt;br&gt;
escribible y la tarea configurada. Las seis estrategias que implementamos se&lt;br&gt;
ejecutan con un comando cada una:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongodump &lt;span class="nt"&gt;--uri&lt;/span&gt; mongodb://127.0.0.1:27017/dataforge_backup &lt;span class="nt"&gt;--out&lt;/span&gt; DIR &lt;span class="nt"&gt;--gzip&lt;/span&gt;
redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 6379 &lt;span class="nt"&gt;--rdb&lt;/span&gt; dump.rdb
neo4j-admin database dump neo4j &lt;span class="nt"&gt;--to-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DESTINO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una estrategia que necesita cuatro piezas coordinadas no se puede probar en un&lt;br&gt;
&lt;code&gt;for&lt;/code&gt; loop. Una que necesita un comando, sí.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. No tiene equivalente conceptual de "copia en frío del almacenamiento".&lt;/strong&gt;&lt;br&gt;
Las estrategias físicas que sí implementamos —copiar los archivos del motor con&lt;br&gt;
el servicio detenido— no existen como operación propia en SQL Server: el&lt;br&gt;
&lt;code&gt;.mdf&lt;/code&gt;, los archivos de log y el backup son cosas distintas y la foto del&lt;br&gt;
almacenamiento no es una operación respaldada por el motor. La distinción&lt;br&gt;
lógico/físico es justamente la que queríamos poder comparar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Los seis compromisos que medimos no aplican a un motor relacional.&lt;/strong&gt; Cada&lt;br&gt;
restricción que encontramos es del motor NoSQL, y son las que hacen un respaldo&lt;br&gt;
interesante de estudiar:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compromiso&lt;/th&gt;
&lt;th&gt;Dónde aparece&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mongorestore&lt;/code&gt; &lt;strong&gt;no acepta un directorio&lt;/strong&gt;: con la carpeta restaura 0 documentos y devuelve código &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CONFIG SET dir&lt;/code&gt; falla con &lt;code&gt;can't set protected config&lt;/code&gt;; hay que usar &lt;code&gt;redis-cli --rdb&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Redis 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SET n:Etiqueta&lt;/code&gt; es error de sintaxis sin el plugin APOC&lt;/td&gt;
&lt;td&gt;Neo4j 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MERGE&lt;/code&gt; &lt;strong&gt;colapsa aristas repetidas&lt;/strong&gt; entre el mismo par de nodos&lt;/td&gt;
&lt;td&gt;Neo4j&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community &lt;strong&gt;exige el motor detenido&lt;/strong&gt; para &lt;code&gt;database dump&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Neo4j&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;database backup&lt;/code&gt; no existe en Community (es de Enterprise)&lt;/td&gt;
&lt;td&gt;Neo4j&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ninguno de esos compromisos tiene equivalente en el modelo relacional. Son&lt;br&gt;
precisamente los que hacen que "verificar un respaldo" no sea sin automático.&lt;/p&gt;
&lt;h2&gt;
  
  
  El hallazgo que ningún manual avisa: el respaldo que "tiene éxito" y no restaura nada
&lt;/h2&gt;

&lt;p&gt;Este es el resultado que más aporta del proyecto, y no aparece en la&lt;br&gt;
documentación.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mongorestore&lt;/code&gt; &lt;strong&gt;no acepta el directorio del dump&lt;/strong&gt;. Si se le pasa la carpeta&lt;br&gt;
completa, imprime un mensaje apodbable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;don't know what to do with file ... skipping...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…restaura &lt;strong&gt;cero documentos&lt;/strong&gt; y &lt;strong&gt;sale con código de salida &lt;code&gt;0&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Es el peor fallo posible en un respaldo: parece un éxito. Un pipeline que&lt;br&gt;
comprueba el código de salida registra un respaldo íntegro que no contiene nada.&lt;/p&gt;

&lt;p&gt;La única forma de detectarlo es &lt;strong&gt;contar lo que vuelve&lt;/strong&gt;. Por eso el ciclo de&lt;br&gt;
prueba del proyecto es siempre el mismo, sin excepción:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;se genera el respaldo;&lt;/li&gt;
&lt;li&gt;se mide su SHA-256;&lt;/li&gt;
&lt;li&gt;se destruyen los datos;&lt;/li&gt;
&lt;li&gt;se restaura;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;se cuenta lo que volvió&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ese quinto paso es el que separa un respaldo de una intención.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seis estrategias, medidas
&lt;/h2&gt;

&lt;p&gt;Cada número de esta tabla salió de una corrida real. El artefacto se vuelve a&lt;br&gt;
leer desde disco para medirlo y para hashearlo.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Motor&lt;/th&gt;
&lt;th&gt;Estrategia&lt;/th&gt;
&lt;th&gt;Artefacto&lt;/th&gt;
&lt;th&gt;Tamaño&lt;/th&gt;
&lt;th&gt;Round-trip medido&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;logico_mongodump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*.bson.gz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30.8 KiB&lt;/td&gt;
&lt;td&gt;1400 → 1400 docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fisico_wiredtiger&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;copia del &lt;code&gt;dbpath&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1.11 MiB&lt;/td&gt;
&lt;td&gt;1400 → 1400 docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rdb_dump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dump.rdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11.3 KiB&lt;/td&gt;
&lt;td&gt;240 → 240 claves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aof_append&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;appendonlydir/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11.4 KiB&lt;/td&gt;
&lt;td&gt;240 → 240 claves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Neo4j&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dump_binario&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;neo4j.dump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;509.1 KiB&lt;/td&gt;
&lt;td&gt;200/594 → 200/594&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Neo4j&lt;/td&gt;
&lt;td&gt;&lt;code&gt;exportacion_cypher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON Lines&lt;/td&gt;
&lt;td&gt;104.0 KiB&lt;/td&gt;
&lt;td&gt;200/594 → 200/594&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;6 de 6&lt;/strong&gt; restauraron exactamente lo que había.&lt;/p&gt;

&lt;p&gt;Una advertencia sobre esas cifras: son de una sola ejecución en una máquina de&lt;br&gt;
escritorio (Linux 6.8, Python 3.11.15). Sirven para &lt;strong&gt;comparar estrategias entre&lt;br&gt;
sí&lt;/strong&gt;, no como referencia de un servidor en producción. El propio índice de&lt;br&gt;
respaldos lo declara.&lt;/p&gt;

&lt;h3&gt;
  
  
  La physical es más rápida de escribir y te quita el servicio
&lt;/h3&gt;

&lt;p&gt;Las dos estrategias físicas —&lt;code&gt;fisico_wiredtiger&lt;/code&gt; y &lt;code&gt;dump_binario&lt;/code&gt;— exigen el&lt;br&gt;
motor &lt;strong&gt;detenido&lt;/strong&gt;. En producción eso no es un detalle de implementación: es una&lt;br&gt;
decisión de arquitectura, porque significa una ventana sin servicio.&lt;/p&gt;

&lt;p&gt;Para la misma base de grafos, la exportación lógica corría &lt;strong&gt;en caliente&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Estrategia Neo4j&lt;/th&gt;
&lt;th&gt;Duración&lt;/th&gt;
&lt;th&gt;¿Motor encendido?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;exportacion_cypher&lt;/code&gt; (JSON Lines)&lt;/td&gt;
&lt;td&gt;420 ms&lt;/td&gt;
&lt;td&gt;Sí&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;dump_binario&lt;/code&gt; (&lt;code&gt;.dump&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;3.48 s&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ocho veces más rápida, pero con el servicio caído. Elijes según qué te duele&lt;br&gt;
más.&lt;/p&gt;

&lt;h2&gt;
  
  
  La tentación que casi arruinó el proyecto
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MERGE&lt;/code&gt; funde en una sola las aristas repetidas entre el mismo par de nodos con&lt;br&gt;
propiedades distintas. Al medir el round-trip faltaban 4 relaciones: &lt;strong&gt;590 de&lt;br&gt;
594&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;590 de 594 parece "casi bien". Ese es el momento exacto en que un respaldo mal&lt;br&gt;
verificado se convierte en un respaldo mal documentado: bastaba relajar la&lt;br&gt;
comparación un 0.7 % para que el número cuadrara y el defecto quedara oculto.&lt;/p&gt;

&lt;p&gt;La tentación era relajar el umbral. Convertir un defecto real en un falso&lt;br&gt;
positivo es exactamente lo que hace que un sistema de respaldo pierda&lt;br&gt;
credibilidad. Con &lt;code&gt;CREATE&lt;/code&gt; en lugar de &lt;code&gt;MERGE&lt;/code&gt;, las 594 se conservan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué no protege cada estrategia
&lt;/h2&gt;

&lt;p&gt;Esta tabla es la que más útil resulta y la que menos se documenta. Una estrategia&lt;br&gt;
que solo declara ventajas no está documentada, está vendida.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Estrategia&lt;/th&gt;
&lt;th&gt;Qué protege&lt;/th&gt;
&lt;th&gt;Qué NO protege&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Completo&lt;/td&gt;
&lt;td&gt;Todo el contenido, sin depender de nada anterior&lt;/td&gt;
&lt;td&gt;La caída del disco que lo contiene; un borrado posterior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incremental&lt;/td&gt;
&lt;td&gt;Lo que cambió desde el último completo&lt;/td&gt;
&lt;td&gt;Depende encadenadamente de todos los completos: falta uno y se rompe la cadena&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diferencial&lt;/td&gt;
&lt;td&gt;Lo que cambió desde el último de su misma estrategia&lt;/td&gt;
&lt;td&gt;Los cambios acumulados desde el completo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instantánea&lt;/td&gt;
&lt;td&gt;El estado en un instante, sin detener el motor&lt;/td&gt;
&lt;td&gt;Corrupción posterior del archivo; exige almacenamiento transaccional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replicación&lt;/td&gt;
&lt;td&gt;Disponibilidad continua: otra copia siempre viva&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No protege contra el borrado&lt;/strong&gt;: un &lt;code&gt;DROP&lt;/code&gt; se replica al instante&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;La última fila es la más contraintuitiva. Replication se suele vender como&lt;br&gt;
respaldo y no lo es: multiplica la disponibilidad y &lt;strong&gt;duplica el error&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verificación y despliegue automatizado
&lt;/h2&gt;

&lt;p&gt;La suite (&lt;code&gt;app/tests/casos.py&lt;/code&gt;, casos T-01..T-27) corre en cada &lt;code&gt;push&lt;/code&gt; mediante&lt;br&gt;
&lt;code&gt;.github/workflows/pruebas.yml&lt;/code&gt;, y la aplicación se crea sola en Render leyendo&lt;br&gt;
&lt;a href="https://github.com/dejameingresar/dataforge-backup-nosql/blob/main/render.yaml" rel="noopener noreferrer"&gt;&lt;code&gt;render.yaml&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
con &lt;code&gt;autoDeploy&lt;/code&gt; y health check en &lt;code&gt;/api/salud&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hay un punto que conviene decir con claridad: &lt;strong&gt;los casos que necesitan motores&lt;br&gt;
reales no pueden correr en el runner de GitHub Actions.&lt;/strong&gt; El runner no tiene&lt;br&gt;
MongoDB, Redis ni Neo4j. La suite no inventa un resultado: esos casos fallan de&lt;br&gt;
forma explícita y la verificación completa se hace contra los motores locales.&lt;/p&gt;

&lt;p&gt;Es la misma lección del otro proyecto del equipo: un &lt;code&gt;n/d&lt;/code&gt; en la pantalla es&lt;br&gt;
correcto; un test verde sin motor detrás no lo sería.&lt;/p&gt;

&lt;p&gt;En el despliegue público los tres motores aparecen como &lt;code&gt;n/d&lt;/code&gt;, porque un servicio&lt;br&gt;
público no expone puertos de base de datos. El despliegue demuestra que la&lt;br&gt;
aplicación arranca y responde; las cifras medidas son del laboratorio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusiones
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Un respaldo no es un archivo: es una cadena.&lt;/strong&gt; Backup, verificación por hash,
restauración y conteo. Se salta un eslabón y el resultado no sirve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El código de salida no es evidencia.&lt;/strong&gt; Los dos fallos más caros de este
proyecto salían con &lt;code&gt;0&lt;/code&gt;. Contar lo que volvió sí lo es.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La verificación tiene que ser estricta o no vale nada.&lt;/strong&gt; Relajar el umbral
de comparación convierte un defecto real en un falso positivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cada familia de motor tiene su propia restricción&lt;/strong&gt;, y documentar &lt;strong&gt;qué no
protege&lt;/strong&gt; una estrategia es más útil que enumerar qué sí.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excluir una tecnología se demuestra en el código.&lt;/strong&gt; En este proyecto, SQL
Server está en la lista de motores rechazados y hay una prueba que lo verifica,
no una nota en el README.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Y el punto que resume todo lo demás: &lt;strong&gt;elegir una tecnología obliga a aceptar sus&lt;br&gt;
compromisos&lt;/strong&gt;. Elegimos estos tres motores porque sus límites son comprobables&lt;br&gt;
con un comando y un &lt;code&gt;count&lt;/code&gt;. Ahí es donde se puede demostrar que un respaldo&lt;br&gt;
funciona, en vez de suponerlo.&lt;/p&gt;

</description>
      <category>nosql</category>
      <category>backup</category>
      <category>mongodb</category>
      <category>redis</category>
    </item>
    <item>
      <title>Los bugs más caros del escaneo de vulnerabilidades no están en el código, están en el pipeline</title>
      <dc:creator>Nicole Rios</dc:creator>
      <pubDate>Sun, 04 Oct 2026 04:22:24 +0000</pubDate>
      <link>https://dev.to/korins707/los-bugs-mas-caros-del-escaneo-de-vulnerabilidades-no-estan-en-el-codigo-estan-en-el-pipeline-lf0</link>
      <guid>https://dev.to/korins707/los-bugs-mas-caros-del-escaneo-de-vulnerabilidades-no-estan-en-el-codigo-estan-en-el-pipeline-lf0</guid>
      <description>&lt;h1&gt;
  
  
  Los bugs más caros del escaneo de vulnerabilidades no están en el código, están en el pipeline
&lt;/h1&gt;

&lt;p&gt;En los laboratorios de la unidad revisamos código con SonarQube, Semgrep, Snyk y&lt;br&gt;
tfsec. Para esta actividad cambiamos el punto de mira: &lt;strong&gt;OWASP Dependency-Check&lt;/strong&gt;&lt;br&gt;
(dependencias de terceros, base de avisos de la OWASP Foundation) y &lt;strong&gt;gosec&lt;/strong&gt;&lt;br&gt;
(análisis estático de Go con reglas mapeadas a CWE). Ninguna de las dos se usó en&lt;br&gt;
los laboratorios.&lt;/p&gt;

&lt;p&gt;La parte interesante no fue el informe final. Fue lo que pasó al intentar que el&lt;br&gt;
escaneo se repitiera solo en cada &lt;code&gt;push&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Los enlaces del proyecto
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repositorio público con el código, los workflows y el informe:&lt;/strong&gt;
&lt;a href="https://github.com/UPT-FAING-EPIS/actividad-grupal-1-sast" rel="noopener noreferrer"&gt;https://github.com/UPT-FAING-EPIS/actividad-grupal-1-sast&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informe de SAST publicado como aplicación web (Vercel, despliegue por
GitHub Actions):&lt;/strong&gt; &lt;a href="https://sast-taskflow-iota.vercel.app" rel="noopener noreferrer"&gt;https://sast-taskflow-iota.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artículo complementario del equipo:&lt;/strong&gt; &lt;a href="https://dev.to/dejameingresar/auditar-dependencias-con-owasp-dependency-check-ei8"&gt;&lt;em&gt;Auditar dependencias con OWASP
Dependency-Check&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  El resultado que importa
&lt;/h2&gt;

&lt;p&gt;La aplicación de caso de estudio (&lt;code&gt;app/main.go&lt;/code&gt;) está escrita &lt;strong&gt;a propósito&lt;/strong&gt; con&lt;br&gt;
las clases de fallo más comunes. Es un objetivo de prueba, no una aplicación de&lt;br&gt;
producción.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gosec &lt;span class="nt"&gt;-no-fail&lt;/span&gt; &lt;span class="nt"&gt;-fmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;informe.json ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;Cantidad&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDIUM&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LOW&lt;/td&gt;
&lt;td&gt;1&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;17&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cinco de los altos son una credencial escrita en el código, una clave privada RSA&lt;br&gt;
embebida, y tres encontrados por &lt;strong&gt;análisis de taint&lt;/strong&gt; (inyección de comandos,&lt;br&gt;
recorrido de rutas, XSS), que no aparecen buscando palabras clave: la herramienta&lt;br&gt;
sigue el valor desde que entra en la función hasta donde se usa.&lt;/p&gt;

&lt;p&gt;Ese 17/5/11/1 es el mismo número que produce el escaneo manual y el que produce&lt;br&gt;
el pipeline. Que coincidan no es casualidad: es la comprobación de que el&lt;br&gt;
automatismo está escaneando lo mismo que la persona.&lt;/p&gt;
&lt;h2&gt;
  
  
  El fallo más caro: un escaneo que no ocurrió pareciendo limpio
&lt;/h2&gt;

&lt;p&gt;gosec &lt;strong&gt;no necesita el compilador de Go para no fallar&lt;/strong&gt;. Sin él, el comando&lt;br&gt;
termina con código de salida &lt;code&gt;0&lt;/code&gt; y escribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Archivos: 0 | Lineas: 0 | Hallazgos: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es indistinguible de "el código no tiene nada". Un panel que muestra una&lt;br&gt;
casilla verde y un &lt;code&gt;0&lt;/code&gt; no está diciendo que el código sea seguro: está diciendo que&lt;br&gt;
no miró nada.&lt;/p&gt;

&lt;p&gt;Esto lo sufrimos de verdad. Corregimos el workflow para que escaneara&lt;br&gt;
&lt;code&gt;./app/...&lt;/code&gt;, lo subimos, y el paso pasó en verde. El informe, en cambio, decía&lt;br&gt;
cero hallazgos. La causa: &lt;strong&gt;&lt;code&gt;go.mod&lt;/code&gt; vive en &lt;code&gt;app/&lt;/code&gt;, no en la raíz del&lt;br&gt;
repositorio&lt;/strong&gt;, así que al ejecutar gosec desde la raíz no había módulo que&lt;br&gt;
resolver y el analizador no tenía nada que cargar:&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="c1"&gt;# mal: sin modulo en la raiz, gosec responde "Files: 0" sin explicar por que&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;gosec -no-fail -fmt=json -out=informe.json ./app/...&lt;/span&gt;

&lt;span class="c1"&gt;# bien: hay que escanear desde el directorio del modulo&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;Escanear&lt;/span&gt;
  &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&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;gosec -no-fail -fmt=json -out="$GITHUB_WORKSPACE/informe.json" ./...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La corrección que de verdad importa no es el &lt;code&gt;working-directory&lt;/code&gt;. Es no trusting&lt;br&gt;
el cero:&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;iss&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;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;::error::0 hallazgos: el escaneo no ocurrio, no salio limpio&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;Un escaneo con cero hallazgos y un escaneo que no se ejecutó producen el mismo&lt;br&gt;
archivo. Si el pipeline no los distingue, no está midiendo nada: está&lt;br&gt;
decorándose. Y una automatización que miente es peor que no tener automatización,&lt;br&gt;
porque ocupa el lugar de una revisión.&lt;/p&gt;
&lt;h2&gt;
  
  
  Los otros cuatro fallos, todos de automatización
&lt;/h2&gt;

&lt;p&gt;Ninguno estaba en el código de la aplicación. Los cuatro estaban en el pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. El workflow estaba escrito para otro proyecto.&lt;/strong&gt; El &lt;code&gt;security.yml&lt;/code&gt;apuntaba&lt;br&gt;
&lt;code&gt;dotnet restore TaskFlow.Api/TaskFlow.Api.csproj&lt;/code&gt; sobre un repositorio que no&lt;br&gt;
tenía ese proyecto, y escaneaba &lt;code&gt;seguridad/banco-vulnerable/&lt;/code&gt;, un directorio que&lt;br&gt;
tampoco existía. El error que salía era &lt;code&gt;MSBUILD : error MSB1009: Project file&lt;br&gt;
does not exist&lt;/code&gt;, que señala un problema de rutas pero no dice que el archivo de&lt;br&gt;
workflow está describiendo otro repositorio. Cuatro ejecuciones seguidas en rojo&lt;br&gt;
por lo mismo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Una acción que dejó de existir.&lt;/strong&gt; El job de tfsec usaba&lt;br&gt;
&lt;code&gt;aquasecurity/tfsec-action@v1&lt;/code&gt;, y el runner respondió &lt;code&gt;Unable to resolve action&lt;br&gt;
aquasecurity/tfsec-action@v1, unable to find version v1&lt;/code&gt;. Las etiquetas de las&lt;br&gt;
acciones son referencias: pueden desaparecer, y cuando lo hacen el job no falla&lt;br&gt;
por código, falla al no resolverse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Un secret ausente que se veía como otro problema.&lt;/strong&gt; El despliegue ejecutaba&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx vercel deploy &lt;span class="nt"&gt;--prod&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--token&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;con &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; sin registrar, y la CLI respondía &lt;code&gt;Error: You defined&lt;br&gt;
"--token", but it's missing a value&lt;/code&gt;. Un mensaje que habla de sintaxis de&lt;br&gt;
comandos cuando el problema es de configuración. La versión que obliga a nombrar&lt;br&gt;
el secreto que falta:&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="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;Verificar secrets de Vercel&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;VERCEL_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;VERCEL_ORG_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_ORG_ID }}&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;for v in VERCEL_TOKEN VERCEL_ORG_ID; do&lt;/span&gt;
      &lt;span class="s"&gt;if [ -z "${!v}" ]; then&lt;/span&gt;
        &lt;span class="s"&gt;echo "::error::Falta el secret $v (Settings &amp;gt; Secrets &amp;gt; Actions)"&lt;/span&gt;
        &lt;span class="s"&gt;exit 1&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
    &lt;span class="s"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Un &lt;code&gt;.gitignore&lt;/code&gt; que se comió un resultado.&lt;/strong&gt; El &lt;code&gt;.gitignore&lt;/code&gt; traía&lt;br&gt;
&lt;code&gt;informe.json&lt;/code&gt;, el mismo nombre que produce el escaneo. El artefacto se generaba,&lt;br&gt;
se subía, y desaparecía antes de llegar a ninguna parte.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lo que la automatización no puede hacer sola
&lt;/h2&gt;

&lt;p&gt;Dos cosas de este ejercicio quedan fuera del alcance de un pipeline, y conviene&lt;br&gt;
decirlo en vez de dejarlas para cuando falle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La base de avisos de la NVD no cabe en un runner.&lt;/strong&gt; Dependency-Check descarga&lt;br&gt;
la base de la NVD, que supera los 400.000 registros. Sin una API key la descarga&lt;br&gt;
inicial tarda &lt;strong&gt;horas&lt;/strong&gt;; con una key gratuita baja a minutos&lt;br&gt;
(&lt;a href="https://nvd.nist.gov/developers/request-an-api-key" rel="noopener noreferrer"&gt;https://nvd.nist.gov/developers/request-an-api-key&lt;/a&gt;). En un job con límite de&lt;br&gt;
tiempo, la primera ejecución nunca va a completar. Lo que decidimos fue no&lt;br&gt;
inventar el resultado: si el informe no existe, el workflow escribe la constancia&lt;br&gt;
y lo dice con un &lt;code&gt;::warning::&lt;/code&gt; en vez de publicar un cero. Registrando el secret&lt;br&gt;
&lt;code&gt;NVD_API_KEY&lt;/code&gt;, el escaneo real se completa sin tocar el workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Las métricas solo existen si alguien las midió.&lt;/strong&gt; El artefacto &lt;code&gt;informe-gosec.json&lt;/code&gt;&lt;br&gt;
está versionado en el repositorio, con fecha y entorno, precisamente para que el&lt;br&gt;
número que se cita en el artículo se pueda contrastar contra el archivo. Un&lt;br&gt;
escaneo que corre y nadie guarda no deja nada que revisar.&lt;/p&gt;
&lt;h2&gt;
  
  
  Publicar el informe, no solo el código
&lt;/h2&gt;

&lt;p&gt;El informe de escaneo sirve poco si leerlo exige clonar el repositorio y abrir un&lt;br&gt;
JSON. El segundo workflow corre el mismo gosec en cada &lt;code&gt;push&lt;/code&gt; a &lt;code&gt;web/**&lt;/code&gt; y publica&lt;br&gt;
el resultado como aplicación web en Vercel, con el detalle por severidad y regla.&lt;/p&gt;

&lt;p&gt;El detalle que costó encontrar: &lt;strong&gt;la CLI de Vercel no recibe el proyecto por&lt;br&gt;
variables de entorno.&lt;/strong&gt; Las lee de &lt;code&gt;web/.vercel/project.json&lt;/code&gt;. Con solo &lt;code&gt;--token&lt;/code&gt;&lt;br&gt;
y &lt;code&gt;--scope&lt;/code&gt; el comando responde &lt;code&gt;You specified VERCEL_PROJECT_ID but you forgot&lt;br&gt;
VERCEL_ORG_ID&lt;/code&gt;, o peor, despliega en un proyecto que no es el que creías. El&lt;br&gt;
workflow ahora escribe ese archivo con los secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .vercel
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"projectId": "%s", "orgId": "%s"}\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERCEL_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERCEL_ORG_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .vercel/project.json
npx &lt;span class="nt"&gt;--yes&lt;/span&gt; vercel@25 deploy &lt;span class="nt"&gt;--prod&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--token&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERCEL_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitación honesta
&lt;/h2&gt;

&lt;p&gt;Automatizar el escaneo garantiza que se repita, no que acierte. Estas&lt;br&gt;
automatizaciones se ejecutan en cada &lt;code&gt;push&lt;/code&gt;, en cada &lt;em&gt;pull request&lt;/em&gt; y una vez por&lt;br&gt;
semana, porque la base de avisos cambia a diario y una vulnerabilidad puede&lt;br&gt;
publicarse después del último &lt;code&gt;push&lt;/code&gt;. Aun así:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Un análisis estático no ve lógica de negocio incorrecta&lt;/strong&gt; si no hay un patrón
peligroso detrás. La base de este ejercicio lo demuestra: una aplicación
deliberadamente insegura en clase de datos, pero sin ninguna regla de las que
busca gosec, daría un informe limpio sin tener un solo problema resuelto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El caso de estudio no se despliega y no debe usarse con datos reales.&lt;/strong&gt;
Publicar el informe es seguro; publicar la aplicación vulnerable, no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gosec&lt;/code&gt; solo analiza Go.&lt;/strong&gt; Sobre un proyecto en C# devuelve &lt;code&gt;Files: 0&lt;/code&gt;, que es
el mismo cero sin información del que hablaba antes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusiones
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;El escaneo que no se ejecutó y el escaneo limpio producen el mismo archivo.&lt;/strong&gt;
La primera defensa no es la herramienta, es un paso que compare &lt;code&gt;0&lt;/code&gt; contra
"no se pudo ejecutar" y aborte si no puede distinguirlos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;En un pipeline, la mayoría de los fallos son de rutas, de secretos y de
versiones, no de vulnerabilidades.&lt;/strong&gt; Ninguno de los cuatro fallos de este
artículo estaba en el código de la aplicación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un mensaje de error que no nombra la causa cuesta más que el fallo.&lt;/strong&gt; El
&lt;code&gt;missing a value&lt;/code&gt; de Vercel ocultaba un secret sin registrar durante una
ejecución completa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un resultado sin su archivo no es evidencia.&lt;/strong&gt; Versionar el informe con fecha
y entorno es lo que permite que alguien lo contraste en lugar de creerlo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La automatización multiplica el valor de un control, y también multiplica
el alcance de un error de configuración.&lt;/strong&gt; Un escaneo manual equivocado se
detecta en la revisión. Un escaneo automático que no escanea pasa desapercibido
mientras todo luzca verde.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lo que queda fuera de alcance, y no es menor: la base de avisos completa no cabe&lt;br&gt;
en un runner sin API key. Un análisis de dependencias que no puede descargar la&lt;br&gt;
base de datos no es un análisis de dependencias, y conviene que el pipeline lo&lt;br&gt;
diga en lugar de mostrar un cero.&lt;/p&gt;

</description>
      <category>security</category>
      <category>sast</category>
      <category>gosec</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
