Auditoría Suprema 2 · Tanda 8 (workspace, última de las 8): el token del doctor autorizaba escritura a todo /api/*
Cuándo
01-09-2026 · commit f7867413 (PR #167). Octava y última tanda de arreglos de la Auditoría Suprema 2 — cierra la serie de 8 tandas repartida entre CreaRack-Pro ([[incident—20260831—auditoria-suprema-2-tanda1-monitoring-racks]], [[incident—20260831—auditoria-suprema-2-tanda3-xss-signage-editor]], [[decision—20260831—tanda-5-auth-agente-backend]], [[incident—20260901—auditoria-suprema-2-tanda6-agente-toma-control-downgrade]]…) y esta última, dedicada al propio workspace (el repo que aloja el gateway MCP, la wiki y las integraciones) en vez del producto.
Síntomas visibles
1 hallazgo ALTA + 14 MEDIA/BAJA, repartidos en 6 áreas del workspace:
METHOD_DOCTOR_TOKENcon alcance real de/api/*completo (functions/api/_middleware.ts): el token dedicado al scriptmethod-doctor.ps1se aceptaba con untimingSafeEqualsin comprobar la ruta — el comentario decía “escritura limitada a ese endpoint” pero el código no lo aplicaba, así que ese mismo token podía volcar D1 vía/api/maintenance/exporto commitear amainvía/api/wiki/content.- CF Access degradaba en silencio: con
CF_ACCESS_ENFORCE=1peroCF_ACCESS_TEAM_DOMAIN/CF_ACCESS_AUDausentes o mal escritos, el middleware caía al modo “solo exigir la cabecera presente” sin avisar — una configuración a medias desactivaba la verificación real. READONLY_BLOCKED_PREFIXESsolo cubría/api/wiki/content: un Bearer readonly podía crear/editar/borrar tasks, news, notes y alerts por los endpoints REST directos, publicar en el Correo y falsearharness-status— justo lo que el MCP ya le prohibía a esas mismas tools por el otro canal.- 6 tools MCP que mutan o gastan cuota de IA sin pasar por el gate readonly;
add_task_commentpermitía comentar en nombre de otra persona; dos handlers podían registrar la misma tool sin que ningún test lo detectase. - Los 4
create_*de Holded devolvían éxito con200 + status:0(contradice la Regla 15 de no-falsear-verificación). - Chat del Correo sin rate-limit por owner;
zoho_linksin validar contramail.zoho.*;memberFromBearerduplicado con comparación no constante en tiempo. bfsImpact/call-graph de la Biblioteca sinmin_confidenceen un placeholder.bind; sin allowlist deedge_type/node_typeen escritura;bib_search_semanticsin redactar secretos (a diferencia debib_ask); repo del hot-cache sin validar owner/name.- El
deletedel Curator de la wiki no borraba el.md(resucitaba al re-indexar); el drift-check per-doc mutaba por defecto en vez dedry_run=true;enrichreemplazaba una línea de front-matter a pelo (ya había roto el build el 04-07). - El
auth_codede OAuth no era single-use atómico (ventana TOCTOU) y no se podaban los códigos de más de 1 día.
Causa raíz
El patrón que atraviesa el hallazgo ALTA y buena parte de los MEDIA es el mismo: un comentario en el código afirmaba una restricción que el código no aplicaba. El comentario de METHOD_DOCTOR_TOKEN decía “limitado a ese endpoint”, pero el if solo comprobaba el token, no la ruta — exactamente el hueco que el token del cron DR (export nocturno de Hetzner STAGE) sí evitaba con su propio startsWith, a pocas líneas de distancia en el mismo fichero. READONLY_BLOCKED_PREFIXES nació pensada para un único endpoint (/api/wiki/content) y no se revisó cada vez que se añadió un nuevo CRUD REST directo (tasks, news, notes, alerts, correo): cada uno asumía que “readonly” ya lo bloqueaba el MCP, sin contar con que ese mismo dato es alcanzable por REST sin pasar por ninguna tool MCP.
Fix aplicado
Commit f786741334f0cad4c9814f625705f99d25def046 (PR #167):
METHOD_DOCTOR_TOKENacotado conurl.pathname.startsWith('/api/harness-status'), mismo patrón que el token DR.- CF Access pasa a fail-closed: sin
CF_ACCESS_TEAM_DOMAINyCF_ACCESS_AUDpresentes a la vez, el JWT no se acepta y la petición sigue al Bearer. READONLY_BLOCKED_PREFIXESampliado atasks,news,notes,alerts,correo,harness-status.- Las 6 tools MCP de mutación/gasto de IA entran en
UNLOGGED_WRITE_TOOLS;add_task_commentdeja de aceptar suplantar autor; test nuevo (test/handler-collision.test.ts) fija que ninguna tool esté registrada en dos handlers. - Los 4
create_*de Holded pasan porholdedResult(). - Rate-limit del chat del Correo por owner;
zoho_linkvalidado contramail.zoho.*;memberFromBearerunificado enfunctions/_lib/staff.tscon comparación en tiempo constante. min_confidenceen el placeholder.binddebfsImpact/call-graph; allowlist deedge_type/node_typetambién en escritura;bib_search_semanticredacta secretos igual quebib_ask; el repo del hot-cache valida owner/name.- El delete del Curator borra también el
.md; el drift-check per-doc usadry_run=truepor defecto;enrichusaapplyFrontMatterLinesen vez de reemplazar una línea a pelo. - El
auth_codede OAuth pasa a consumo single-use atómico; poda de códigos de más de 1 día. - 168 tests pasan (17 ficheros).
Lecciones
- Un comentario que describe una restricción no es la restricción: el código correcto (el token DR con
startsWith) ya existía a pocas líneas de distancia y no se replicó al token del doctor. - Una lista de bloqueo nacida para un caso concreto (
READONLY_BLOCKED_PREFIXES) necesita revisarse cada vez que se añade un endpoint mutante nuevo, no solo cuando llega una auditoría — el mismo dato era accesible por dos caminos (MCP y REST) y solo uno tenía guardia. - “Fallar en silencio hacia el modo menos seguro” (CF Access con configuración parcial) es un antipatrón recurrente: toda comprobación de auth con configuración opcional debería fallar cerrado por defecto, no abierto.
Preventivos futuros
- Esta tanda resuelve el ALTA y los 14 MEDIA/BAJA que traía en el mismo commit; el resto de MEDIA/BAJA de la Auditoría Suprema 2 fuera del ámbito workspace sigue su segunda pasada según
supercontext/auditoria-suprema-2/PLAN_ARREGLOS.md. - Con esta tanda se cierran las 8 de la Auditoría Suprema 2 (10/10 áreas, 300 hallazgos iniciales); queda pendiente actualizar el estado consolidado en Supercontexto y, si aplica, las fichas del Atlas de Arquitectura del workspace (
workspace-ws1aws6) que documentan estos endpoints.
Véase también
- [[incident—20260831—auditoria-suprema-2-tanda1-monitoring-racks]]
- [[incident—20260831—auditoria-suprema-2-tanda3-xss-signage-editor]]
- [[incident—20260901—auditoria-suprema-2-tanda6-agente-toma-control-downgrade]]
- [[feature—security—auditoria-suprema-2-tanda-1-core]]
- [[decision—20260831—tanda-5-auth-agente-backend]]
- [[decision—20260829—sa4-g2-command-smuggling-cerrado]]