Dev.to Security 🔐 Cybersecurity 👁 0 📖 12 min read

Addendum R2: La tasa que faltaba, medida sobre las dos clases que faltaban

Addendum R2 — la tasa que faltaba, medida sobre las dos clases que faltaban Este artículo sustituye como lectura al anterior —«Addendum: corregí mi propio instrumento y el número bajó de 807 a 612»: https://dev.to/ale

Addendum R2 — la tasa que faltaba, medida sobre las dos clases que faltaban

Este artículo sustituye como lectura al anterior —«Addendum: corregí mi propio instrumento y el número bajó de
807 a 612»: https://dev.to/alexisromtz7/addendum-corregi-mi-propio-instrumento-y-el-numero-bajo-de-807-a-612-5172—
cuyo cuerpo se publicó sin el Markdown con que fue escrito: la plataforma guardó el texto y descartó la sintaxis.
El contenido es el mismo; la forma no. Se republica para que se pueda leer lo que se midió. El original sigue vivo y
sin editar
, como registro de lo que el entorno de publicación hizo con él (§9).

Este documento no reescribe el anterior. El addendum publicado con RC 2 y RC 4
(ADDENDUM_ARTICULO_20261030.md, blob 723dda49, vivo en Dev.to) queda intacto byte a byte, porque un acto
público no se corrige por edición: se corrige por adenda. Lo que sigue sustituye solo su sección «La tasa que
faltaba», y solo en la parte que dejó abierta.

Huella de esta revisión: HUELLA-ADDENDUM-R2-TPR-RC1-RC5-20261030. Se declara como texto en código y no como
comentario HTML por la razón medida en §9.

1. Lo que quedaba abierto, dicho con la cifra al lado

El addendum anterior cerró la tasa de RC 2 y RC 4: 33 casos, 100% formal, 15.2% sustantiva. Y declaró su
propio residuo en la misma página: RC 1 (60) y RC 5 (308) seguían sin tasa.

Eso significa que 368 de las 612 observaciones publicadas — el 60,1% del numerador — no tenían veredicto. Un
cliente que preguntara «¿cuál es tu tasa de falsos positivos?» recibía una respuesta que valía para 33 casos y se
presentaba como si valiera para 612. Esa era la debilidad, y no estaba escondida: estaba escrita.

Ya no está abierta.

2. La medición

Misma disciplina que la vez anterior, y en el mismo orden: primero la predicción, después la lectura.

Se escribió un pre-registro (preregistro_tpr_rc1_rc5_20261030.json, 129 líneas, blob 05eaead8) con las clases
sustantivas, el sesgo declarado y cinco falsadores, antes de abrir un solo run: o un solo uses:. Después se
re-descargaron los 163 archivos de workflow del lote por blob SHA desde la API de GitHub y se verificó su
sha256 contra el manifiesto: 163/163 idénticos, 0 fallos. El motor reprodujo el reparto exacto:
RC 1 60 · RC 2 30 · RC 4 3 · RC 5 308.

clase lectura resultado
RC 5 REF_MUTABLE ¿el uses: citado existe en el crudo y su ref es realmente flotante? 308/308, 0 falsos positivos
RC 1 AUTORIDAD_CONTAMINADA ¿el job tiene escritura y ejecuta código en la misma línea citada? 60/60, 0 falsos positivos
Control negativo RC 5 de 1321 uses:, 1013 están fijados a SHA de 40: ¿cuántos marca el motor? 0 de 1013
Control negativo RC 1 164 jobs ejecutan código de terceros sin scope de escritura: ¿cuántos marca? 0 de 164

Los dos controles negativos son la parte que importa. Un motor que marcara todo habría dado 1321 en lugar de 308, y
174 en lugar de 60. No los da: discrimina en los dos sentidos de la conjunción.

3. La tasa sustantiva: aquí es donde el número se encoge

Formal 100% no es la cifra que se puede vender, y el addendum anterior ya lo dijo para RC 2. Para RC 1 y RC 5
el efecto es mayor, porque estas dos clases se miden por ocurrencia y no por riesgo.

RC 5 — 308 ocurrencias no son 308 riesgos: son 31

El barrido de RC 5 no deduplica. Una misma acción repetida en veinte jobs produce veinte hallazgos. En los 163
archivos hay 1321 valores uses:, de los cuales 308 son flotantes… y esos 308 son solo 31 referencias
distintas
. Publicar 308 sin decirlo sería inflar el numerador por la puerta de atrás.

clase ocurrencias distintas qué es
Primera parte (actions/*) 239 17 etiqueta móvil de GitHub sobre código de GitHub. Patrón documentado
Tercero, etiqueta flotante 42 5 pnpm/action-setup@v6, dtolnay/rust-toolchain@stable, oven-sh/setup-bun@v2, rust-lang/crates-io-auth-action@v1, vercel/next.js/.github/actions/pr-auto-label@canary
Workflow reutilizable flotante 10 6 un workflow entero de otro repo ejecutado con el token de este
Placeholder sin sustituir 17 3 ver §4 — clase que la predicción no tenía
Imagen docker sin digest 0 0 predije 8; no hay una sola en los 10 repos

La superficie de terceros real: 52 ocurrencias y 11 referencias distintas — el 16,9% de las ocurrencias y el
35,5% de las distintas. Las otras 256 se reparten en 239 actions/*, que para un cliente es ruido (el control del
ref lo tiene GitHub, no el atacante que el cliente teme), y 17 placeholders que no resuelven y que van contados como
clase propia en §4, no como terceros.

Corrección con cadena (HUELLA-ADDENDUM-R2-CORRECCION-PORCENTAJE-TERCEROS-20261030). La primera versión de este
párrafo, escrita horas antes y nunca publicada, decía 22,4% y «las otras 239». Ese porcentaje es correcto para 69
ocurrencias, no para 52: la frase había sumado al numerador las 17 de R7, que son rutas $/.github/… del propio
repositorio del cliente y por tanto no son código de un tercero. La tabla de arriba nunca dijo 69; decía 42 + 10.
El número de la prosa y el de su propia tabla no medían lo mismo, y nada los comparaba. Se conserva el literal
equivocado a la vista porque un addendum que corrige cifras no puede editarse en silencio —esa es la misma regla que
lo hizo nacer—. La corrección viaja con el censo:
INVESTIGACION/evidencia/censo_tpr_rc1_rc5_20261030.json, campo superficie_de_terceros.correccion_con_cadena.

RC 1 — 60 hallazgos, 10 sostienen el nombre del código

RC 1 se llama AUTORIDAD_CONTAMINADA. La conjunción que mide —escritura y ejecución en el mismo job— es
correcta y da 60/60 formal. Pero solo una parte de esas 60 líneas ejerce o entrega la autoridad:

clase n qué es
A1 — escritura remota ejercida 9 gh run cancel, gh pr comment, gh api --method POST: el token es el instrumento de esa misma línea
A2 — descarga a ejecución 1 `curl https://rustwasm.github.io/…/init.sh -sSf \
A3 — hereda terceros 28 {% raw %}pnpm install, cargo build, npx -y [email protected], cargo binstall, gh corriendo con el token disponible
A4 — mutación local 5 git config sin push: la autoridad está declarada y no se ejerce
A5 — código del propio repo 11 pnpm run build, node scripts/…: el que ve el token es el dueño del repo
A7 — lexema sin ejecución 6 ver §4

Autoridad ejercida o entregada: 10 de 60 (16,7%). Ese es el número que la palabra «contaminada» puede defender.

4. Las dos clases que la predicción no tenía

En dos días consecutivos, el mismo patrón: la taxonomía se queda corta y la clase nueva aparece en el mismo lugar
donde el motor lee bien.
Ayer fue NO_SUMIDERO (16 de 33). Hoy son dos, y las dos se declaran en vez de encajarse.

R7 — placeholder sin sustituir: 17 ocurrencias, 3 literales, 3 repositorios y 6 archivos. Los bytes crudos
contienen referencias locales escritas con el placeholder de plantilla de GitHub ($/…) que nunca se sustituyó:
$/.github/actions/setup 5 veces, todas en sveltejs/svelte ci.yml; $/.github/actions/issues-helper
11 veces en vitejs/vite —6 en bot.yml, 3 en issue-labeled.yml, 1 en issue-close-require.yml, 1 en
lock-closed-issues.yml—; y $/.github/workflows/notify-resolved-issues.yml 1 vez en nuxt/nuxt release.yml.
Los bytes son genuinos del origen: el git hash-object de los seis archivos coincide uno por uno con el blob_sha
que declara el manifiesto, así que no es un artefacto de la descarga. Su severidad queda NO_MEDIDO, por la misma
razón que NO_SUMIDERO: 0 si se lee que un uses que no resuelve no custodia nada, y «el hallazgo más fuerte» si se
lee que el archivo declara una intención que no ejecuta y quien lo audite creerá que está fijado. Resolverlo exige
saber si el repo genera ese archivo o lo commitea tal cual, y el archivo no lo dice.

Corrección con cadena (HUELLA-ADDENDUM-R2-R7-POR-ARCHIVO-20261030). La versión anterior de este párrafo, escrita
horas antes y nunca publicada, nombraba facebook/react como uno de los repositorios con placeholders. Medido sobre
los 163 workflows ya verificados por sha256: facebook/react tiene 26 workflows y cero ocurrencias, y los 17
están en svelte (5), vite (11) y nuxt (1). El conteo nunca cambió —17 ocurrencias, 3 literales—; la atribución
sí. Es el tercer caso del mismo defecto en la jornada: un campo estructurado correcto al lado de una frase que no
se deriva de él
, igual que el 22,4% de §5. La corrección viaja con el censo, campo
clase_no_prevista.correccion_con_cadena.

A7 — el lexema casa sin que nada se ejecute: 6 de 60. Un echo " - NPM name: …" casa con el patrón de npm.
Un yq '.allowBuilds[]=false' -i pnpm-workspace.yaml casa porque el nombre del archivo dice pnpm. No es un
defecto del motor —su dominio declarado es léxico por línea— pero sí es ruido para un cliente, y por eso se cuenta
aparte y se publica: es la forma en que un falso positivo sobrevive a la corrección del motor.

5. Cuatro defectos del lector, encontrados antes de publicar

La lección de la vez anterior se aplicó con saña: el lector también se equivoca, y cuando se equivoca declara
culpable al instrumento correcto.
Un lector independiente de mesa, sin el parser del motor, encontró cuatro:

  1. permissions: {} leído como {'{}': 'write'} → RC 1 inflado de 60 a 121 (61 falsos rojos).
  2. El uses: de job contado dos veces → RC 5 inflado de 308 a 319 (11 falsos rojos).
  3. Valores de permiso entrecomillados (contents: 'read') conservando las comillas → un job real quedaba fuera: 59 de 60.
  4. run: plegado en varias líneas leído solo en la primera → una línea citada aparecía truncada.

Los cuatro eran del lector. El motor no cambió una línea: su salida formal se sostiene 368/368.

6. Predicción contra medición, sin maquillar

predicción (pre-registrada) medido veredicto
RC 5: 308 TP, 0 FP, ±4 308 / 0 cumplida
RC 1: 60 TP, 0 FP, ±6 60 / 0 cumplida
31 a 60 literales distintos, punto 45 31 cumplida al borde, por un literal
Reparto RC 5: 240 / 55 / 8 / 3 / 2 / 0 239 / 42 / 0 / 10 / 0 / 17 dirección correcta, tres desvíos
Terceros distintos: 12 ± 6 11 cumplida
A1+A2: 9 ± 6 10 cumplida

Tres desvíos que se publican porque se midieron: la imagen docker sin digest no existe en los 10 repos (predije
8), el workflow reutilizable flotante es el triple de lo predicho (3 → 10, y es la clase más fuerte de las
cuatro), y apareció una clase que no estaba en la taxonomía (R7, 17). El punto de 45 contra 31 no es un fallo de
método: es la diferencia entre estimar cuántas ocurrencias habría y contar cuántas referencias distintas hay.

7. Lo que sigue sin tasa, y por qué se dice

SC1 — 211 observaciones, y no todas son la misma cosa. No son del gate: son el recuento de sustitución y cadena
del propio arnés. Con este acto se miden 368 de 612 observaciones (60,1%) — pero ese denominador no es el de
hallazgos, y confundirlos es el defecto que este addendum ya cometió una vez con otra cifra. De las 244 sin censo,
156 son REVIEW por diseño del contrato (SC1_JOB_WRITE_SCOPE_REVIEW 153 + SC1_ROOT_READ_ALL_REVIEW 3): son
inventario, no hallazgo, y no hay tasa que asignarles porque no afirman una vulnerabilidad. 55 sí son conjunción
real
(SC1_ROOT_PERMISSIONS_MISSING 36 + SC1_ROOT_WRITE_INHERITED 19). Y 33 son la clase corregida (RC 2 30

  • RC 4 3), validada por revisión de dominio con cadena, no por censo. Sobre el denominador que un CTO querría —el de las que afirman una vulnerabilidad, 456— la cobertura es 368/456 = 80,7%, y lo que falta censar son 55 casos, el 9,0% del total, no 211. Lo que se publica como 60,1% sigue siendo correcto mientras el denominador sea «observaciones»; deja de serlo en cuanto el denominador es «hallazgos».

Huella de esta revisión: HUELLA-ADDENDUM-R2-DENOMINADOR-20261030 (§MEDICIÓN CON UNIDAD, aplicado al propio
enunciado: el 60,1% no estaba mal, estaba midiendo otra población).

Y tres residuos más, declarados: RC 5 emite job: '-', así que no dice qué job consume la acción y la
severidad por job queda NO_MEDIDA; el residuo propio del motor sigue vigente (env: con secretos + ejecución de
terceros sólo cae bajo RC 1 si hay scopes de escritura); y la lectura sustantiva es de mesa, sobre el YAML, sin
ejecutar los workflows: no mide si una acción de terceros es hostil hoy, mide si el ref es fijable y si la autoridad
se ejerce.

8. La frase que sí se puede publicar

612 observaciones, de las cuales 456 afirman una vulnerabilidad. Censadas: 368 — el 80,7% de los hallazgos y el
60,1% de las observaciones.
De las 244 restantes: 156 son inventario REVIEW por contrato, 55 son conjunción
real sin censar
y 33 son la clase corregida, validada por revisión de dominio.
TPR formal 368/368. Control negativo: 1013 refs fijadas sin marcar y 164 jobs sin escritura sin marcar.
Sustantiva: 10 de 60 hallazgos de autoridad ejercen o entregan la credencial; 11 referencias distintas de
terceros en 308 ocurrencias de ref flotante; 17 referencias que no resuelven —5 en sveltejs/svelte ci.yml,
11 en cuatro workflows de vitejs/vite (6 de ellas en bot.yml), 1 en nuxt/nuxt release.yml—, con
severidad NO_MEDIDO.

No hay un número redondo y no lo va a haber. Lo que hay es un instrumento que se equivocó 195 veces, lo publicó, se
corrigió, midió su propia tasa, encontró cuatro defectos en su lector y los publicó también, y dejó a la vista lo
que no pudo medir. Eso es lo que un CTO puede auditar. Un «1000 vulnerabilidades» no.

9. Nota de publicación: el formato declarado y el formato ejecutado

El addendum anterior se escribió en Markdown y se publicó sin él. Medido hoy contra la API pública (GET
/api/articles/4950016
, HTTP 200, published_at 2026-10-31T00:18:48Z): el body_markdown almacenado contiene
0 **, 0 comillas invertidas, 0 | y 0 ##. La pérdida no está en el render: está en la fuente.
Por eso el formato no se recupera cambiando de modo en el editor — hay que reescribir el cuerpo.

Y hay un detalle que sí es un mecanismo, no un accidente: de los dos comentarios HTML del cuerpo publicado, el que
abría línea perdió su < (!-- HUELLA-TPR-MEDIDA…, visible como texto) y el que iba en línea detrás del -->
sobrevivió intacto (<!-- HUELLA-NO-SUMIDERO-MEDIDO…). Regla falsable con n=1 cuerpo: Dev.to elimina el primer <
de una línea que empieza por <; los siguientes de la misma línea sobreviven.
No es «el principio del cuerpo», como
se declaró en la primera lectura: el cuerpo empieza por texto, no por <. Una sola observación no es una regla: se
publica con su n y su falsador.

Consecuencia operativa, aplicada en este mismo documento: ninguna huella de esta revisión viaja como comentario
HTML; viajan como texto en código
, en la cabecera y en el pie. Es el mismo defecto que este documento mide en los
otros: una intención declarada —el formato, la huella, el permissions:— que el entorno de ejecución no ejecuta.

Huella de esta sección: HUELLA-ADDENDUM-R2-NOTA-FORMATO-20261030.

Fuente de verdad de las cifras: INVESTIGACION/evidencia/censo_tpr_rc1_rc5_20261030.json (blob a2a7aa26,
tras las dos correcciones con cadena del 30-Oct-2026) y
INVESTIGACION/evidencia/preregistro_tpr_rc1_rc5_20261030.json (blob 05eaead8), ambos sobre los mismos 163
archivos de workflow con identidad verificada por sha256 y git hash-object.

Huella de esta revisión: HUELLA-ADDENDUM-R2-R7-LOCUS-20261030.

Huella de esta revisión: HUELLA-ADDENDUM-R2-R7-POR-ARCHIVO-20261030 y HUELLA-ADDENDUM-R2-DENOMINADOR-20261030. Los
seis archivos con R7 se verificaron uno por uno con git hash-object contra el manifiesto; el denominador de 456
sale de composicion y de los recuentos SC1 del lote, no de la prosa.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.