Files
hechakuaa_bot/CHANGELOG.md
markosbenitez 05b2f84eb8 security: vault v3 — PBKDF2 + envelope encryption (DEK/KEK)
Reemplaza derivación SHA256 por PBKDF2-HMAC-SHA256 (600k iter) con salt
aleatorio por tenant + DEK de 32 bytes aleatoria por cifrado. Robar la DB
sin FERNET_KEY o FERNET_KEY sin la DB ya no alcanza para comprometer credenciales.
Compatibilidad v1 preservada para migración. Agrega migrar_vault_v3.py con
dry-run, pre-flight check y backup atómico.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:04:51 -03:00

8.7 KiB

Changelog — Pedrito Hechakuaa

Todos los cambios notables se documentan acá.

Formato: Keep a Changelog
Versiones: Semantic Versioning


[Unreleased]


[0.4.0] — 2026-05-26

Security

  • Envelope encryption v3 en vault.py — credenciales cifradas en dos capas independientes:
    • DEK (Data Encryption Key): 32 bytes aleatorios, única por operación de cifrado.
    • Wrapping Key: derivada de FERNET_KEY con PBKDF2-HMAC-SHA256, 600k iteraciones, salt de 16 bytes aleatorio por tenant. ~200ms por derivación — fuerza bruta inviable incluso con GPU.
    • Formato en DB: {"v":3,"s":"<salt_b64>","k":"<dek_enc>","p":"<payload_enc>"}
    • Propiedades: robar la DB sin FERNET_KEY es inútil; robar FERNET_KEY sin la DB también.
  • Cache de wrapping keys (lru_cache) — evita ejecutar PBKDF2 600k iter por cada tenant en cada ciclo del poller. Cache key incluye fingerprint del KEK para invalidar si cambia FERNET_KEY.
  • Compatibilidad v1 preservadadescifrar_credenciales detecta automáticamente formato v1 (Fernet token puro) vs v3 (JSON). No se generan nuevas v1.

Added

  • scripts/migrar_vault_v3.py — migración segura de credenciales v1 → v3:
    • Verifica que todos los tenants v1 sean descifrables ANTES de tocar la DB (aborta si alguno falla).
    • Backup automático de la DB antes de cualquier escritura.
    • Flag --dry-run para ver qué haría sin modificar nada.
    • Commit atómico: o migra todos o no migra ninguno.

[0.3.1] — 2026-05-26

Security

  • /health/history requiere authAuthorization: Bearer <HEALTH_AUTH_TOKEN>; sin token devuelve 401 con header WWW-Authenticate. Sin auth configurada, retorna 401 igualmente.
  • chat_ids enmascarados en historial — reemplazados por hash SHA256[:8] opaco (usr_a3b4c5d6); preserva correlación de eventos sin exponer Telegram IDs (PII).
  • /health público mínimo — sin auth devuelve solo {status, timestamp}. Con auth, respuesta completa con métricas internas. Elimina exposición de conteo de tenants y timing del poller.
  • Env vars borradas del proceso — después de cargar Settings(), se eliminan de os.environ: FERNET_KEY, TELEGRAM_BOT_TOKEN, PJ_USUARIO_ENC, PJ_PASSWORD_ENC, WEBHOOK_SECRET, HEALTH_AUTH_TOKEN, INVITE_CODE. Protege contra /proc/<pid>/environ.
  • Race condition TOCTOU en invite codes eliminadaSELECT + UPDATE reemplazado por UPDATE WHERE codigo=? AND usado=0; rowcount=0 indica código no válido o ya usado. Imposible que dos requests simultáneos consuman el mismo código.
  • Timing oracle en validación de código mitigado — delay mínimo garantizado de 800ms en _paso_codigo() independientemente del resultado; secrets.compare_digest() para comparación contra código del .env.
  • Respuestas a usuarios no registrados reducidas al mínimo_rechazar_no_registrado ya no revela la arquitectura de comandos; texto libre de usuarios sin registro recibe silencio total.
  • Webhook inválido devuelve 404 — en vez de 403, no confirma la existencia del endpoint.
  • Nginx rate limiting documentado en DEPLOY.md: limit_req_zone 30r/s para webhook, 10r/min para health; headers X-Content-Type-Options, X-Frame-Options.
  • HEALTH_AUTH_TOKEN agregado a config.py y a la guía de .env en DEPLOY.md.

[0.3.0] — 2026-05-25

Added

  • DockerDockerfile (imagen ghcr.io/astral-sh/uv:python3.12-bookworm-slim), docker-compose.yml con restart: unless-stopped y healthcheck, .dockerignore.
  • Health check HTTP (health.py) — servidor aiohttp en puerto 8080:
    • GET /health — estado del bot (ok/degraded)
    • GET /health/history — últimos N eventos del audit log
    • Watcher loop que alerta al admin via Telegram si el poller no actualiza en N horas
  • Schema migrations — directorio migrations/001_initial_schema.sql, función run_migrations() en database.py; idempotente con CREATE TABLE IF NOT EXISTS. Tabla schema_migrations lleva registro de versiones aplicadas.
  • Webhook opt-inPOST /webhook en aiohttp valida X-Telegram-Bot-Api-Secret-Token con compare_digest y pone el Update en application.update_queue de PTB. Se activa con WEBHOOK_URL en .env; vacío = long-polling.
  • aiohttp>=3.9 como nueva dependencia.
  • WEBHOOK_URL, WEBHOOK_SECRET, HEALTH_PORT, HEALTH_ALERT_HOURS en config.py.
  • Poller notifica a health vía callback on_poll_done(tenant_count).
  • revisar_ahora() retorna int (cantidad de tenants revisados).

Changed

  • init_db() reemplazado por run_migrations() en main.py; init_db() queda como alias para compatibilidad con scripts.
  • main.py arranca HealthServer antes del poller; elige polling vs webhook según config; registra webhook con Telegram en modo webhook; elimina webhook al detener.
  • DEPLOY.md actualizado con guía completa de 20 secciones: DNS Bluehost/cPanel (con mensaje exacto en inglés para delegar), Nginx, Certbot/SSL, Docker, webhook, health check, rate limiting.

[0.2.1] — 2026-05-25

Fixed

  • Campos del API del PJ corregidos — la API real usa fechaNotificacion (no fecha), descripcionDespacho (no juzgado), descripcionCircunscripcion (no circunscripcion). NotificacionPendiente.__init__ ahora usa fallback or para ambos nombres.
  • PDF callback — usaba cod_notificacion_origen como ID de actuación; corregido a cod_actuacion_caso (campo codActuacionCaso de la API).
  • 403 en listar_notificaciones — manejado explícitamente como CSJAuthError en vez de caer en raise_for_status() genérico.

[0.2.0] — 2026-05-25

Added

  • Multi-tenantdatabase.py con SQLite vía aiosqlite; tabla tenants con credenciales cifradas por chat_id; list_active_tenants(), save_tenant(), delete_tenant().
  • Onboarding flow (onboarding.py) — máquina de estados de 8 pasos: código de invitación → consentimiento → nombre → tono → timezone → cédula → password → guardado.
  • Códigos de invitación — tabla invite_codes, validación de un solo uso atómica, fallback al código maestro del .env; scripts/generar_codigo.py.
  • Audit log — tabla audit_log, función audit.log() fire-and-forget; cobertura de todos los eventos de onboarding, polling, comandos y errores.
  • Rate limiting (utils/rate_limiter.py) — ventana deslizante por chat_id, 20 req/60s; reset en /resetear.
  • FERNET_KEY rotation (scripts/rotar_fernet_key.py) — re-cifra todos los tenants de forma atómica; hace backup de .env antes de modificar; aborta si algún tenant falla.
  • Comando /exp — búsqueda de expedientes por número o carátula.
  • Comando /pdf — descarga PDF de la actuación más reciente via permisoFirmado.
  • Tono personalizable por abogado: formal / cotidiano / informal.
  • Zona horaria configurable por abogado.
  • TELEGRAM_EXTRA_IDS para autorizar IDs adicionales sin onboarding.
  • DEPLOY.md — primera versión de la guía de despliegue en VPS Ubuntu con systemd.

Changed

  • poller.py itera sobre todos los tenants activos (era single-tenant).
  • Snapshots por tenant: data/snapshot_{chat_id}.json (era data/snapshot.json global).
  • vault.py usa derivación de clave Fernet por chat_id: base64(SHA256(FERNET_KEY + str(chat_id))).

Security

  • Contraseña del PJ borrada de memoria inmediatamente tras cifrar (del password en onboarding).
  • Mensaje de Telegram con la contraseña eliminado del chat vía delete_message antes de procesar.

[0.1.0] — 2026-05-25

Added

  • Login al sistema judicial apps.csj.gov.py: GET /login → cookie cookiesession1POST /autenticador/login → bearer token (TTL 1h).
  • Polling de GET /Notificaciones/PorRecibir cada N minutos en horario hábil (lun-vie, 7-18hs).
  • Diff vs snapshot JSON local — solo notifica notificaciones con codNotificacionOrigen nuevo.
  • Relogin automático transparente al expirar el token.
  • Bot de Telegram con comandos: /start, /notif, /estado, /ayuda, /salir.
  • Credenciales del PJ cifradas con Fernet en .env (scripts/cifrar_credenciales.py).
  • scrub() en utils/sanitize.py — sanitiza bearer tokens, JWTs, cookies y passwords antes de pasar cualquier dato externo a los logs.
  • Logging estructurado con structlog (JSON en prod, ConsoleRenderer en dev vía isatty).
  • Configuración vía pydantic-settings — único punto de config de la app (config.py).
  • Stack base: httpx, python-telegram-bot[ext] v21, aiosqlite, cryptography, structlog, pydantic-settings, apscheduler.
  • Manejo de señales SIGINT/SIGTERM para shutdown limpio.