# Proposal — Auditoría de seguridad y hardening (Odoo 18.0)

## Why

Detectamos ataques automatizados cada vez más sofisticados que la heurística de fail2ban no frena,
y somos responsables de la seguridad de nuestra infra y la de nuestros clientes. Necesitamos (a) un
proceso de auditoría **reproducible** tipo "checklist de prevuelo" para verificar la resiliencia
ante fuerza bruta, acceso indebido a datos vía API y vectores inadvertidos; y (b) corregir cinco
hallazgos ya confirmados leyendo el propio código de `website_sale_aplicoop`.

## Objetivo

Dejar la infra Odoo con una postura de seguridad auditable y repetible, y cerrar los hallazgos
H1–H5 con tests que impidan su regresión.

## Alcance

- **Solo nuestra infra** (autorización directa, sin terceros). Producción está tras nginx/traefik
  con TLS.
- Auditoría **activa autorizada** (fuerza bruta controlada contra cuenta de test y fuzzing de
  endpoints) desde la máquina Kali de la red y desde `localhost` vía SSH, preferentemente contra un
  clon de staging o en ventana de mantenimiento con snapshot previo.
- Remediación de los hallazgos confirmados en `website_sale_aplicoop` (+ `groups_id` en la server
  action de mandatos SEPA de `account_banking_mandate_batch`).

## No-objetivos

- No se audita ni escanea infra de terceros ni hosting ajeno.
- No se corrige aquí ningún hallazgo **nuevo** que surja durante la auditoría activa: se registra en
  el findings register y se aborda en un change posterior (salvo severidad crítica, que se escalará).
- No se modifica `ocb/` ni los addons OCA originales.

## What Changes

- **Proceso de auditoría**: se añaden entregables de documentación (`docs/SECURITY_AUDIT_CHECKLIST.md`,
  `docs/SECURITY_AUDIT_RUNBOOK.md`, `docs/SECURITY_FINDINGS.md`) y scripts no destructivos en
  `scripts/security/` (probe de ACL vía API, probe de IDOR, harness de fuerza bruta, PoC de CSRF,
  recon web) parametrizados con cuentas de test.
- **Fix H1 (Alta)** — ACL con `group_id` vacío que concede write+create global sobre `group.order` y
  `group.order.slot`: se reemplaza por ACLs de mínimo privilegio (lectura interna/portal explícitas;
  escritura solo manager). Los writes de los controladores usan `sudo()` y no dependen de estas ACLs.
- **Fix H2 (Alta)** — record rule de `product.supplierinfo` con `domain=[(1,'=',1)]` que expone todos
  los precios de proveedor al portal. El portal usa `supplierinfo` para pintar el **origen** del
  producto y el **proveedor principal** en la web, así que no se elimina sin más: o se preparan esos
  valores server-side (vía `sudo()` en el controlador, según la regla "sin lógica en QWeb") o se acota
  el dominio a los productos accesibles por el usuario, sin exponer campos de coste/precio.
- **Fix H3 (Media)** — endpoints de cambio de estado con `csrf=False`: se convierten a `type="json"`
  (bloquea el POST de formulario cross-site), tomando como plantilla `confirm_order_from_portal`.
- **Fix H4 (Media)** — endpoints de lectura/ajax que solo comprueban `exists()`+`state`: se añade
  comprobación de pertenencia al grupo reutilizando `_validate_user_group_access` /
  `_get_consumer_group_for_user`.
- **Fix H5 (Baja)** — `t-raw` inyectado en `<script>`: se sustituye por un patrón de salida seguro.
- **Hardening de config**: `admin_passwd`, `list_db=False`, `proxy_mode=True`, password de BD fuerte,
  rate-limit/cabeceras en nginx, jails de fail2ban (web + API). Documentado como requisitos; los
  cambios de infra no versionable se ejecutan en el servidor, los versionables (`groups_id` en la
  server action) en el repo.

## Capabilities

### New Capabilities
- `eskaera-access-control`: cómo `website_sale_aplicoop` restringe acceso a datos y acciones —
  ACL de mínimo privilegio, alcance de `product.supplierinfo`, autorización por pertenencia al grupo
  de consumo en todos los endpoints, protección CSRF en endpoints de estado, y escapado de salida en
  plantillas. Cubre H1–H5.
- `security-audit-process`: proceso repetible de auditoría — reglas de compromiso/autorización,
  checklist de prevuelo, runbook de auditoría activa, scripts de prueba y registro de hallazgos.
- `infra-hardening`: requisitos de endurecimiento de producción — `odoo.conf`, reverse proxy
  (nginx/traefik) con TLS/rate-limit/cabeceras, fail2ban (web + API), y gestión de secretos/BD.

### Modified Capabilities
- (ninguna — no existen specs previas en `openspec/specs/`)

## Impact

- **Addons afectados**: `website_sale_aplicoop` (security CSV + record rules, controladores,
  plantilla, tests, bump de `__manifest__.py`); `account_banking_mandate_batch` (`groups_id` en la
  server action).
- **Addons NO afectados**: `ocb/`, los addons OCA originales, y el resto de custom (solo se auditan;
  cualquier hallazgo se registra, no se modifica en este change).
- **Nuevas rutas en el repo**: `docs/SECURITY_*`, `scripts/security/`.
- **Impacto UI/website/QWeb**: al convertir endpoints a `type="json"` hay que ajustar el transporte
  en el JS de `website_sale_aplicoop` (solo transporte, sin lógica). La plantilla
  `load_from_history_templates.xml` cambia su forma de inyectar JSON. Sin cambios visibles de UX.
- **Infra (no versionable)**: `odoo.conf`, config de nginx/traefik y fail2ban en el servidor de prod.

## Riesgos

- La auditoría activa (fuerza bruta/fuzzing) puede degradar o afectar a producción: mitigado con
  clon de staging o ventana de mantenimiento, snapshot previo y cuentas de test dedicadas (nunca
  contra cuentas reales, por riesgo de lockout/DoS de usuarios legítimos).
- Convertir endpoints a `type="json"` cambia el contrato de request/response: riesgo de regresión en
  el carrito/checkout del portal; mitigado con tests y verificación end-to-end.
- Recortar el acceso a `product.supplierinfo` podría romper la visualización de precios si algún
  punto del portal dependía de la ACL abierta: verificar que el pricing se computa vía `sudo()`.

## Criterios de aceptación

- Los probes `odoo_acl_probe.py` e `idor_probe.py` deniegan tras los fixes lo que permitían antes
  (write/create de `group.order`, lectura de `supplierinfo` ajeno, datos de grupos ajenos).
- El PoC de CSRF funciona antes del fix y falla después.
- El harness de fuerza bruta acaba con la IP de Kali baneada por fail2ban en `/web/login` y en la
  ruta API.
- Tests de `website_sale_aplicoop` en verde:
  `docker-compose run odoo odoo -d odoo --test-enable --stop-after-init -u website_sale_aplicoop`.
- El checklist de prevuelo cubre los tres frentes + infra, con criterio pass/fail por ítem, y el
  findings register queda con H1–H5 en estado resuelto/verificado.
