El número

49 hallazgos sobre 36 workflows leídos, en una sola pasada estática. De esos 36 workflows, 26
tienen al menos un hallazgo: 1 crítico, 23 de severidad alta y 25 de severidad baja.

La cifra no viene de una corazonada ni de una captura de pantalla. Esa misma medición se corrió
antes sobre el commit eaa56e88 y volvió a correrse el 13 de noviembre sobre 7f1785ea: los dos
sujetos dieron 49 hallazgos, 24 fallos y 25 revisiones. Cuando un instrumento repite el número
sobre dos árboles distintos, el número deja de ser una opinión sobre ese commit y pasa a ser una
propiedad de esa configuración.

El caso que no es higiene

.github/workflows/npm-audit-claude-remediation.yml, job remediate. El job declara escritura en
contents y en pull-requests, y su cuerpo ejecuta curl contra un extremo remoto. Su entrada es
el texto de los avisos del registro de npm.

Ese es el punto. Un aviso de npm es un documento que cualquier tercero puede publicar en el
registro; no es código del repositorio ni texto que el mantenedor controle. Ese texto entra a un job
que puede escribir sobre el árbol y abrir pull requests. La política del repositorio dice que el job
audita dependencias; lo que el workflow permite es que un documento ajeno produzca una escritura con
la identidad del repositorio.

El mismo workflow aparece en el código GATE_RC_2_SUMIDERO_IMPUDO por usar actions/checkout como
sumidero, y en GATE_RC_1_AUTORIDAD_CONTAMINADA por la ejecución remota. Son dos invariantes
distintos midiendo la misma línea de código desde dos ángulos: el sumidero que recibe contenido ajeno
y la autoridad que ese sumidero hereda.

El remedio no es un if. Es partir el job en dos: uno lee el aviso y no tiene autoridad, otro recibe
un dato ya validado y escribe.

El secreto en la línea de comando

.github/workflows/publish-php-sdk.yml, job publish. Un secrets se interpola directamente en un
run: que hace curl a packagist. La interpolación ocurre antes de que el shell corra, así que el
valor queda dentro del comando: en el argv del proceso, en el log si el shell ecoa la línea, y en
cualquier mensaje de error que la imprima.

Es el único hallazgo crítico de los 49, y el único que no admite discusión sobre severidad: la
credencial no se está pasando, se está escribiendo.

Once jobs con autoridad sobre código que no controlan

GATE_RC_1_AUTORIDAD_CONTAMINADA aparece once veces. Los sujetos son deploy-image.yml,
deploy-image-staging.yml, deploy-nuq-postgres.yml, deploy-playwright.yml, deploy-redis.yml y
publish-go-sdk.yml, entre otros, ejecutando docker pull, docker manifest create,
git fetch --force --tags o go build en jobs con scopes=packages o scopes=contents.

Ninguno de esos comandos es exótico. Lo que está fuera de lugar es la combinación: un job que puede
publicar paquetes ejecutando binarios que descarga en el momento. La autoridad no se justifica por lo
que el job hace, sino por lo que puede llegar a ejecutar.

Siete workflows donde la autoridad no se lee del archivo

Siete workflows no declaran permissions en la raíz: eval-prod.yml, npm-audit.yml,
test-dotnet-sdk.yml, test-go-html-to-md-service.yml, test-php-sdk.yml, test-ruby-sdk.yml y
test-server.yml. Heredan el default del repositorio o de la organización, que no vive en el archivo.

Esto es lo que hace el hallazgo distinto de un lint. Un revisor que lee el workflow ve una cosa; el
runtime puede estar aplicando otra. La configuración no es incompleta: es ilegible desde donde se
revisa.

Punto ciego — lo que esta pieza no afirma

No afirma que algo de esto se haya explotado. Lo que se midió es la configuración estática de 36
workflows en un commit concreto; no se ejecutó ningún workflow ni se leyeron los permisos por
defecto de la organización, que no son legibles sin privilegios de administración.

No afirma que los 25 hallazgos de severidad baja sean fallos. Se llaman revisiones porque el
instrumento no puede decidir por sí solo si el alcance de escritura está justificado; eso lo decide
quien conoce la intención del job.

No afirma que los secretos interpolados aparezcan realmente en los logs. No se consultó ningún log
de ejecución de ese workflow.

No afirma nada sobre exploits, sobre terceros ni sobre intención. Una configuración que permite un
efecto no es la prueba de que el efecto haya ocurrido.

CTA único

Si mantienes un repositorio con workflows de GitHub Actions, corre .venv/bin/python
tools/auditar_supply_chain.py --repo <owner>/<name>
sobre el tuyo y mira cuántos jobs tienen
escritura y ejecutan código que no controlas. El instrumento es de solo lectura y no necesita clonar.