Volver a la wiki

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:

  1. METHOD_DOCTOR_TOKEN con alcance real de /api/* completo (functions/api/_middleware.ts): el token dedicado al script method-doctor.ps1 se aceptaba con un timingSafeEqual sin 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/export o commitear a main vía /api/wiki/content.
  2. CF Access degradaba en silencio: con CF_ACCESS_ENFORCE=1 pero CF_ACCESS_TEAM_DOMAIN/CF_ACCESS_AUD ausentes 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.
  3. READONLY_BLOCKED_PREFIXES solo 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 falsear harness-status — justo lo que el MCP ya le prohibía a esas mismas tools por el otro canal.
  4. 6 tools MCP que mutan o gastan cuota de IA sin pasar por el gate readonly; add_task_comment permitía comentar en nombre de otra persona; dos handlers podían registrar la misma tool sin que ningún test lo detectase.
  5. Los 4 create_* de Holded devolvían éxito con 200 + status:0 (contradice la Regla 15 de no-falsear-verificación).
  6. Chat del Correo sin rate-limit por owner; zoho_link sin validar contra mail.zoho.*; memberFromBearer duplicado con comparación no constante en tiempo.
  7. bfsImpact/call-graph de la Biblioteca sin min_confidence en un placeholder .bind; sin allowlist de edge_type/node_type en escritura; bib_search_semantic sin redactar secretos (a diferencia de bib_ask); repo del hot-cache sin validar owner/name.
  8. El delete del Curator de la wiki no borraba el .md (resucitaba al re-indexar); el drift-check per-doc mutaba por defecto en vez de dry_run=true; enrich reemplazaba una línea de front-matter a pelo (ya había roto el build el 04-07).
  9. El auth_code de 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):

Lecciones

Preventivos futuros

Véase también

Subir