Skip to content

feat(platform): onRequestError, reporte de errores del navegador y runJob (0.7.0) - #16

Merged
donchelo merged 4 commits into
mainfrom
feat/platform-observabilidad-0.7.0
Oct 29, 2026
Merged

donchelo merged 4 commits into
mainfrom
feat/platform-observabilidad-0.7.0

Conversation

@donchelo

@donchelo donchelo commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Por qué

La auditoría de cableado de errores (35 repos, 28-oct) mostró que @ai4u/platform no tiene hoy: (1) un gancho para los errores del servidor que Next no pasa por withApiHandler, (2) ningún reporte de errores del navegador (todos los error.tsx solo hacen console.error), ni (3) una forma estándar de registrar crons y trabajos en segundo plano (varios crons responden 200 aunque fallen, y varios after() no hacen flush). Este PR añade las tres piezas, de forma aditiva: no cambia withApiHandler, getLogger, configureTransport ni el logger.

Qué añade (v0.7.0, minor)

  • @ai4u/platform/instrumentation → onRequestError(err, request, context): firma verificada contra la documentación de Next 15/16; registra error, digest, ruta sin querystring, método y contexto del router; nunca lee headers ni cookies; hace flushLogs; no-op fuera del runtime Node; nunca lanza. Uso: export { onRequestError } from "@ai4u/platform/instrumentation".
  • @ai4u/platform/client → installClientErrorReporter y reportClientError: addEventListener (no pisa handlers existentes), idempotente, dedupe de 60 s, tope de 5 por sesión, trunca mensaje y stack, solo viaja location.pathname, sendBeacon con fallback a fetch keepalive. Ignora errores de carga de recursos, Script error. y ResizeObserver loop.
  • @ai4u/platform/client-errors → createClientErrorHandler: receptor para app/api/client-error/route.ts.
  • @ai4u/platform/jobs → runJob(name, fn, { log?, rethrow?, tenant? }): registra inicio y fin con duración, log.error si lanza o si el resultado trae errors/errores > 0, y siempre flushLogs en finally.

⚠️ Seguridad: el endpoint del cliente no tiene autenticación

El navegador del visitante no tiene credenciales de servicio, así que createClientErrorHandler es público. Mitigaciones incluidas: solo application/json, tope de tamaño (413), mismo origen (Sec-Fetch-Site/Origin, 403), límite por IP en memoria (429), validación estricta de forma, saneado y truncado de campos, claves sensibles descartadas, respuesta 204 sin eco. El cliente no puede fijar nivel, servicio ni component del log. Límites conocidos (documentados en README y en el módulo): el límite por IP es de mejor esfuerzo en serverless (cada instancia tiene su Map), y las cabeceras de origen las puede falsificar un cliente que no sea navegador, así que no sustituye a un WAF. Los campos recibidos son datos no confiables.

Para quien integre

  • Si la app tiene proxy/middleware con autenticación, /api/client-error debe quedar pública o el reporte recibe 401 y se pierde.
  • installClientErrorReporter va en un componente "use client" con useEffect; error.tsx/global-error.tsx deben llamar reportClientError.
  • El chequeo de origen compara con x-forwarded-host/host: si un proxy reescribe el host puede dar 403; probar en el primer repo que lo adopte.
  • Todo error del navegador entra como ERROR: vigilar el volumen la primera semana (el fixer nocturno los agrupará).

Verificación

  • Tests escritos primero y en rojo (con las fuentes ocultas: 4 archivos fallan por no cargar), luego verdes. Prueba de mutación: sin el flushLogs() en el finally de runJob fallan 3 de 7 tests de jobs.
  • Aquí, en kernel: npm ci, tsc --noEmit sin errores, npm test 11 archivos / 116 tests verdes (31 nuevos), npm run build correcto, y el dist/ reconstruido es idéntico al commiteado (git diff packages/platform/dist vacío).
  • NO verificado: onRequestError y la ruta de cliente dentro de una app Next real ni con el bundler Edge de Turbopack (la firma se verificó por documentación); sendBeacon y el flujo de origen en navegadores reales (solo con stubs); nada en producción.
  • Código portado desde el espejo ai4u-com-co/platform (donde se escribió por error primero): el espejo se sobrescribe en cada publicación, por eso este PR es la fuente de verdad.

Paso humano para publicar

Tras revisar y mergear: tag platform-v0.7.0 (lo dispara mirror.yml hacia ai4u-com-co/platform con tag v0.7.0). Los consumidores se actualizan con el bump bot. Hasta que exista el tag, ningún repo puede usar estas piezas.

🔒 Endurecimiento del logger (commits 2 y 3)

Una revisión de seguridad de otro PR mostró que un err crudo puede llevar claves dentro del mensaje (p. ej. googleapis manda la clave de API como ?key=… en la URL y los errores de red incluyen la URL). Se arregla en el origen, para todo el ecosistema:

  • serializeErr aplica redactSecrets a message y stack: parámetros de query sensibles (key, token, access_token, signature…) → =[redacted]; Bearer/Basic; JWT; formatos de clave conocidos (sk-, sk-ant-, sb_secret_, AIza…, ghp_, github_pat_); credenciales en URL. No redacta URLs enteras (se perdería diagnóstico).
  • Tope de entrada: message 16 384 caracteres, stack 32 768 (con …[truncado]). Se redacta primero sobre max + 4096 y se recorta DESPUÉS, para que una credencial a caballo del corte no sobreviva a trozos.
  • ReDoS corregido (hallazgo de la revisión automática): la regex del JWT era cuadrática con entradas que repiten eyJ… sin puntos (≈6 400–7 000 ms con "eyJaaaaa-".repeat(20000)). Ahora todos los patrones con prefijo usan (?<![A-Za-z0-9_-]) (el motor no reinicia a mitad de una racha) y sin cotas superiores en los tokens, de modo que un token largo se enmascara entero: las cotas intermedias dejaban visible un JWT con un segmento > 4096 y la cola de una clave > 512 (hallazgos de la revisión). Con el anclaje y el tope de entrada el coste es lineal: 4,5 ms. Tests de rendimiento (< 200 ms) para 10 entradas patológicas.
  • Límites conocidos: no cubre secretos sin formato reconocible fuera de un parámetro con nombre sensible, ni nombres de parámetro como auth/code/session; sk-+8 caracteres puede dar un falso positivo; no limpia registros ya existentes en platform_logs.

Verificación final (kernel)

tsc sin errores, npm test 12 archivos / 133 tests, build reproducible (git diff packages/platform/dist vacío tras segundo build).

🤖 Generated with Claude Code

https://claude.ai/code/session_011UCi86Fu9p1HD1KNPsZXWG

claude Bot and others added 4 commits October 28, 2026 19:21
…nJob — 0.7.0

Aditivo: no cambia withApiHandler, getLogger ni configureTransport. dist/ regenerado
y reproducible. Código escrito originalmente en el espejo ai4u-com-co/platform.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UCi86Fu9p1HD1KNPsZXWG
…sibles en message y stack

Añade redactSecrets(text) (pura, idempotente, regex lineales) y la aplica a
message y stack de los errores del logger, y a String(err) cuando no es Error.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UCi86Fu9p1HD1KNPsZXWG
…e entrada

- JWT y prefijos de clave (AIza, ghp_, github_pat_, sb_*, sk-) usan lookbehind
  (?<![A-Za-z0-9_-]) en vez de \b para no reiniciar a mitad de una racha; los
  cuantificadores abiertos quedan acotados.
- serializeErr recorta message a 16 384 y stack a 32 768 caracteres antes de redactar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UCi86Fu9p1HD1KNPsZXWG
…es de recortar

- Se quitan las cotas superiores de los cuantificadores de JWT y claves: consumen toda
  la racha del token (el lookbehind y el tope de entrada mantienen el coste lineal).
- serializeErr redacta una porción max+4096 y DESPUÉS recorta el resultado, para que una
  credencial a caballo del corte no filtre un fragmento.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UCi86Fu9p1HD1KNPsZXWG
@donchelo
donchelo marked this pull request as ready for review October 29, 2026 00:40
@donchelo
donchelo merged commit 906f683 into main Oct 29, 2026
1 check passed
@donchelo
donchelo deleted the feat/platform-observabilidad-0.7.0 branch October 29, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant