# GitHub AI Scan detectó dos riesgos de Shell, pero el control de seguridad pasó

> Canonical: https://vxi.me/es/posts/github-ai-scan-pr-security-checks/
> Locale: es-es
> Published: 2026-10-16
> Updated: 2026-10-16
> Markdown: https://vxi.me/es/posts/github-ai-scan-pr-security-checks.md

# GitHub AI Scan detectó dos riesgos de Shell, pero el control de seguridad pasó

Hoy abrí el [PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) en un repositorio público de pruebas, con dos patrones de Bash deliberadamente inseguros. Aproximadamente 81 segundos después, `github-advanced-security[bot]` había dejado dos comentarios en Files changed: encontró ambos riesgos.

Sin embargo, en la [ejecución de GitHub Actions #38835686338](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/actions/runs/38835686338) el trabajo `github-advanced-security` terminó con `Success`. Los avisos de seguridad son reales, y el indicador verde también.

El día anterior, GitHub había anunciado la [disponibilidad general (GA) de AI Scan for pull requests](https://github.blog/changelog/2026-10-15-ai-scan-for-pull-requests-generally-available/). La pregunta que quería resolver no era si el escáner detecta código sospechoso, sino algo más útil: **¿por qué un PR puede recibir avisos de vulnerabilidades y aun así mostrar su control de seguridad como correcto?**

La distinción es fundamental: **que AI Scan se ejecute correctamente no significa que el código sea seguro**. Sus hallazgos son recomendaciones (`advisory`), no bloqueos obligatorios para fusionar cambios.

## Dos patrones Bash peligrosos: qué detectó exactamente

Puse los archivos únicamente en `ai-scan-test/` dentro de un PR temporal. No modifiqué código de producción ni usé credenciales o servidores reales. El archivo completo está en el [commit fijo `29e24722d3`](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/blob/29e24722d3f8868322da3aa28ec01c9648f90726/ai-scan-test/vulnerable-deploy.sh). Las siguientes líneas son **inseguras a propósito; no deben utilizarse en producción**:

```bash
eval "systemctl restart ${service_name}"

cmd="curl -fsSL http://${host}/${path} -o /tmp/svc.conf"
$cmd
```

El primer problema está en la función que reinicia un servicio. `service_name` llega desde el exterior y se interpola en `eval`. Shell vuelve a interpretar el texto resultante como un comando. Si el nombre contiene sintaxis maliciosa, deja de ser simplemente un nombre de servicio. El [comentario del bot en la línea 17](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992720) lo clasificó como CWE-78 y recomendó eliminar `eval`, pasar argumentos entrecomillados y limitar qué servicios pueden reiniciarse.

El segundo caso funciona de otro modo. El script guarda un comando `curl` entero en `cmd` y luego ejecuta `$cmd` sin comillas. La separación de palabras durante la expansión permite que `host` o `path`, si son controlables, modifiquen los argumentos y opciones que recibe `curl`. El [comentario de la línea 27](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992738) también señaló el riesgo de manipulación al descargar configuración mediante `http://` sin cifrar.

| Ubicación | Hallazgo de AI Scan | Comentario original |
| --- | --- | --- |
| Línea 17, `eval` | Entrada reinterpretada como sintaxis de Shell (CWE-78) | [Ver comentario](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992720) |
| Línea 27, `$cmd` | Inyección de argumentos/opciones y riesgo de HTTP sin cifrar | [Ver comentario](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992738) |

No conviene describir ambos casos como exactamente la misma ejecución arbitraria de comandos. En el primero, la entrada vuelve a pasar por el intérprete de Shell. En el segundo, el mecanismo confirmado es principalmente la **inyección de argumentos u opciones**: la falta de comillas no demuestra por sí sola que pueda ejecutarse cualquier otro comando independiente.

Hay otra limitación importante: el fixture **no ocultaba sus vulnerabilidades**. Su [README](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/blob/29e24722d3f8868322da3aa28ec01c9648f90726/ai-scan-test/README.md) y los comentarios del script contienen `INTENTIONAL VULNERABILITY (CWE-78)` y la categoría esperada `String injection`. Los ejemplos concretos de explotación del bot no estaban copiados en el código, pero las etiquetas podrían haber facilitado la detección. El 2/2 de esta prueba no es una tasa de detección de vulnerabilidades reales sin etiquetar.

También se puede comprobar la cronología. El [PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) se creó el **2026-10-16 a las 01:32:31 UTC** y los dos comentarios aparecieron a las **01:33:52 UTC**: una diferencia de **81 segundos**. Es una observación única entre la creación del PR y la publicación de los comentarios, no un promedio de duración del escáner.

Las dos fallas introducidas intencionalmente recibieron avisos, pero no abrí un PR distinto con código completamente normal como control negativo. Por tanto, tampoco medí la tasa de falsos positivos.

## Dos comentarios de vulnerabilidad y un Actions en verde

La [ejecución dinámica #38835686338](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/actions/runs/38835686338) se llamaba `Code scanning AI findings on PR #1`. GitHub registró que el job `github-advanced-security` finalizó con `success` a las **01:33:34 UTC**. Los comentarios llegaron a las **01:33:52 UTC**, es decir, **18 segundos después**.

Primero se puso verde el control, después aparecieron los avisos de seguridad. Esa secuencia basta para entender que `Success` no equivale a «no hay vulnerabilidades».

![Diagrama de GitHub AI Scan PR #1: dos riesgos Bash preetiquetados recibieron comentarios advisory, el job terminó con éxito a las 01:33:34 UTC y los avisos aparecieron a las 01:33:52 UTC; el bloqueo del merge es independiente](/images/posts/github-ai-scan-pr/ai-scan-advisory.es-es.svg)

*Esquema elaborado a partir del [PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) público y su [ejecución de Actions](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/actions/runs/38835686338), **no es una captura de pantalla de GitHub**. Los 81 segundos van desde la creación del PR hasta los comentarios; no son el tiempo interno del escáner. Cada idioma muestra su propia versión del SVG.*

Eso tampoco implica un fallo de la herramienta. La [documentación oficial de AI Scan](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections) define los hallazgos como **advisory**. No hacen fallar el code scanning check y no pueden usarse por sí solos para imponer un bloqueo de merge mediante Rulesets. Sirven para llamar la atención del revisor, no para decidir por él si se fusiona el código.

| Señal de GitHub | Qué significa realmente |
| --- | --- |
| Comentarios en línea de AI Scan | Posibles riesgos para autor y revisores; no bloquean la fusión directamente |
| Job dinámico de AI Scan: `Success` | La tarea de escaneo terminó; puede haber comentarios de riesgo igualmente |
| CodeQL u otros escáneres compatibles con Rulesets | Pueden imponer condiciones según la configuración real; hay que verificar la regla |
| Required checks y revisión humana | Que un PR se bloquee ante fallos, ausencias o falta de aprobación depende de las opciones del repositorio |

GitHub documenta por separado la [protección de fusión para code scanning](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/set-merge-protection). Si quieres evitar que se fusione código con hallazgos graves sin resolver, debes configurar un mecanismo que realmente sea obligatorio; no confundir una recomendación de AI Scan con un required check.

**No probé** un Ruleset bloqueando la fusión de este PR peligroso. Por eso tampoco puedo inferir de `Success` que GitHub permita fusionar cualquier PR vulnerable. Lo observado es más concreto: **dos comentarios de riesgo coexistieron con un trabajo de AI Scan marcado como correcto**.

## Antes de activarlo: CodeQL, disponibilidad y créditos

La prueba se realizó en un **repositorio público de una cuenta personal**. Según la [documentación de la GA de octubre de 2026](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections), AI Scan se puede habilitar en repositorios públicos personales, pero no en los privados personales. Los repositorios públicos de organizaciones pueden usarlo; los privados o internos de una organización deben cumplir los requisitos de licencia de GitHub Code Security o GitHub Advanced Security. No se exige una licencia adicional de Copilot solo para utilizar AI Scan.

CodeQL default setup **no es un prerrequisito**. Durante la prueba quedó registrado como `not-configured` y aun así AI Scan dejó dos comentarios. La [documentación oficial](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections) confirma que no depende de haber configurado CodeQL por defecto ni de que su análisis finalice correctamente.

Tampoco es un escaneo retrospectivo de todo el historial del repositorio. Examina cambios de PR que cumplen los requisitos, incluidos ciertos casos de Shell/Bash, Terraform HCL o Dockerfile que CodeQL no cubre. Los PR creados desde un Fork o por Dependabot quedan fuera del alcance descrito para esta GA. Esos equipos necesitarán otras vías de revisión y análisis.

Para habilitarlo, comprueba el interruptor AI Scan en la configuración Code scanning del repositorio. En organizaciones, revisa también las políticas y permisos de las funciones de seguridad.

Tras la GA, el consumo se mide mediante **Code Security AI Credits**. La cuenta que paga, los créditos incluidos y los límites de presupuesto siguen la [política de facturación de GitHub](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections). No confirmé el cargo final de esta prueba, así que no afirmaré que costó `$0`. Si tu repositorio genera muchos PR, consulta antes los créditos y los presupuestos estrictos en Billing.

## Cómo bloquear de verdad una fusión peligrosa

Los [dos comentarios](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) recomiendan una corrección convencional: no volver a pasar datos a `eval` y no montar un comando Shell entero como cadena para expandirla. Una forma más segura de llamar a las herramientas sería:

```bash
# 调用形式示意：service_name 和 url 仍需单独校验
systemctl restart "$service_name"
curl -fsSL "$url" -o /tmp/svc.conf
```

Es **una dirección para corregir el problema, no un parche de seguridad que haya vuelto a probar**. `service_name` necesita una lista de valores permitidos. La URL de descarga debe exigir HTTPS, limitar hosts y redirecciones y, según la importancia del archivo, comprobar su integridad.

A nivel de repositorio, integra CodeQL u otro escáner que realmente pueda bloquear fusiones mediante Rulesets/required checks adecuados. Después verifica que las ramas protegidas rechacen los checks fallidos o ausentes. Los comentarios de AI Scan deben seguir siendo evaluados por los revisores; que el job dinámico esté verde no justifica ignorarlos.

Al terminar, [cerré PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) **sin fusionarlo** y borré la rama de prueba. La rama pública `main` se mantuvo en el commit `87624c26a5e6`. Una consulta REST del propietario confirmó que AI Scan se había restaurado a `disabled`. Cerrar un PR no borra sus comentarios ni los commits fijados, así que los enlaces públicos permiten seguir comprobando las pruebas.

Esta muestra positiva, pequeña y claramente etiquetada, demuestra que AI Scan puede aportar avisos útiles y localizados. Pero **que termine el escaneo y que una fusión sea segura y obligatoriamente controlada son dos cosas diferentes**. Lo primero descubre problemas; lo segundo debe configurarse y verificarse en el repositorio.

Para otra frontera de seguridad, el [análisis del hook local pre-push de OpenQodex](/posts/openqodex-prepush-review-fail-open/) examina qué pasa cuando una revisión con IA falla antes de ejecutar Git push.

*Fecha de la prueba: 16 de octubre de 2026 (UTC).*
