ADR-017 · Google API scope strategy · evitar scopes restricted (no CASA)¶
Fecha: 2026-04-27 Source:
/srv/projects/cis/cis-plan/DECISIONS.md(do not edit here — re-split desde la fuente)
Contexto
Auditoría del 2026-04-27 sobre el GCP project vps-cis (number 508618651235, owner companiadeinnovaciondesantiago@gmail.com) reveló que el código de cis-admin y cis-inbox solicita scopes que Google clasifica como restricted:
https://www.googleapis.com/auth/gmail.readonly(cis-admin ·core/google_auth.py+ cis-inbox ·services/google_oauth.pyyservices/gmail_api_ingester.py)https://www.googleapis.com/auth/drive.readonly(cis-admin ·core/google_auth.py)
Los scopes restricted exigen un CASA Tier 2 security assessment anual de un assessor independiente (Bishop Fox / Schellman / Leviathan), con costo aproximado de USD 5.000–15.000 inicial + reassessment anual + 4–8 semanas de tiempo. CIS no tiene Workspace pagado en innovacionsantiago.cl (verificado con el user 2026-04-27), por lo que no puede usar service account con domain-wide delegation para evitar OAuth interactivo.
Sin CASA y sin Workspace, cualquier user que intente conectar Gmail/Drive con scopes restricted recibe error de Google ANTES de ver el consent screen. Razón por la que el BACKLOG lo marcaba "bloqueador rojo" sin diagnóstico.
Decisión
Evitar scopes restricted. El consent screen del project vps-cis solo declara los siguientes scopes Google (más los OIDC openid/email/profile):
| Scope | Clasificación Google | Uso en CIS |
|---|---|---|
gmail.send |
sensitive | enviar correos en nombre del user (Claudia drafting, respuestas desde cis-inbox) |
calendar.events |
sensitive | sync de obligaciones tributarias (cis-admin/obligaciones) y agenda (Claudia) |
drive.file |
non-sensitive | importar archivos que el user explícitamente abre con Drive Picker (cis-admin/documentos) |
Funcionalidad que ANTES requería scopes restricted se reemplaza así:
- Lectura de Gmail personal del user (cis-inbox) → IMAP + app-password per-user. El user genera un app-password en
myaccount.google.com/apppasswordsy lo pega en cis-inbox (mismo patrón que el pollerintercambio@). Implementación:services/imap_ingester.pyya existente; pendiente endpoint UI de configuración per-user (Fase 1b post-ADR). - Lectura de Drive del user (cis-admin/documentos) → Drive Picker UI con scope
drive.file. El user clickea archivos manualmente; CIS no puede listar todo el Drive ni descubrir archivos por sí solo. Trade-off de UX aceptado. - Gmail API ingester (cis-inbox ·
gmail_api_ingester.py) → DEPRECATED. Cascarón conraise NotImplementedErrorapuntando a este ADR. Borrar en cleanup posterior.
Alternativas consideradas
- Pagar CASA Tier 2 (~USD 5–15k/año + reassessment anual). Rejected: costo prohibitivo para una operación de la escala de CIS, y agrega 4–8 semanas de bloqueo cada vez que se modifica el scope set.
- Adquirir Google Workspace en
innovacionsantiago.cl(~USD 6/user/mes) y usar service account con domain-wide delegation. Rejected por el user: no se justifica para los volúmenes actuales y no resuelve el caso "user externo conecta su Gmail personal a cis-inbox". - Mantener scopes restricted en código + dejar feature como dormida hasta tener CASA. Rejected: el código que pide scopes no aprobados falla en runtime con
invalid_scope, ensucia logs y confunde al próximo dev. Mejor remover y dejar el camino limpio. - Migrar todo a IMAP + SMTP, sin Gmail API en absoluto. Rejected:
gmail.send(sensitive, no requiere CASA) habilita threading correcto conIn-Reply-To/Referencesy branding "via Gmail" que SMTP no replica. Sirve al caso "Claudia responde en hilo desde la cuenta del user".
Consecuencias
- Operacional: reduce el alcance de cosas que CIS puede hacer automáticamente con cuentas Google personales. La ingesta queda atada a app-password (UX peor: 2–3 min de setup por user vs 1 click). Drive deja de soportar "import all" — solo archivos seleccionados con Picker.
- Verificación T&S: con solo
gmail.send+calendar.events+drive.file, la verificación de Google T&S (no CASA) requiere domain ownership + privacy policy real con Limited Use disclosure + demo video corto. Tiempo estimado de aprobación: 1–7 días, sin assessor externo. - Privacy policy: obligatoria en
https://innovacionsantiago.cl/privacycon contenido específico de los 3 scopes elegidos. Hoy/privacydevuelve el SPA fallback de cis-www (catch-all) — pendiente Fase 2 del rollout (ADR-017): página estática real con Limited Use disclosure, sirvida sin SPA fallback. - Cobertura del caso "mail personal del user": queda servido vía IMAP + app-password per-user. Endpoint de configuración pendiente (Fase 1b).
- Cobertura de "envío en nombre del user" (BACKLOG 4.4): habilitado con
gmail.sendpost-verificación T&S. - Cobertura de obligaciones calendar sync:
calendar.eventsestá OK sin CASA — no se rompe. - Reversibilidad: si en el futuro el flujo de caja permite CASA, se pueden re-agregar scopes restricted al consent screen con un nuevo ADR. El refactor IMAP del Fase 1b queda como capacidad adicional (no se quita).
Implementación
Cambios aplicados 2026-04-27:
cis-admin/backend/app/core/google_auth.py:12-19· SCOPES =[openid, userinfo.email, userinfo.profile, gmail.send, calendar.events, drive.file]. Quitados:gmail.readonly,drive.readonly.cis-inbox/backend/app/services/google_oauth.py:25-30· SCOPES =[openid, email, profile, gmail.send]. Quitado:gmail.readonly.cis-inbox/backend/app/services/gmail_api_ingester.py· header doc actualizado a DEPRECATED +ingest_via_gmail_api()levantaNotImplementedErrorcon mensaje apuntando a este ADR.cis-inbox/backend/app/services/imap_ingester.py:188-204· dispatch paraauth_type == "oauth2"ooauth_refresh_tokenretorna stats con error claro, sin importar el módulo deprecado.cis-inbox/frontend/components/SettingsClient.tsx:50-65· texto user-facing actualizado: "Gmail (OAuth — solo envío)" + nota sobre app-password para ingest.
Pendientes para cerrar el loop completo (no son parte de este ADR pero quedan en backlog):
- Fase 2 · privacy policy real en
cis-www(/privacyy/termscon Limited Use disclosure, sin SPA fallback en Caddy). - Fase 3 · verificar dominio
innovacionsantiago.clen Google Search Console (DNS TXT en Cloudflare) + agregar scopes en GCP consent screen + agregar redirect URIs faltantes (/api/v1/google/callbacken cis-admin ·/api/v1/accounts/google/callbacken cis-inbox · JS originhttps://inbox.innovacionsantiago.cl). - Fase 4 · submit T&S verification con justificación de scopes + demo video.
- Fase 1b · endpoint UI per-user para configurar IMAP app-password en cis-inbox (
/accounts/imap/connect) + Drive Picker UI en cis-admin/documentos.
Relaciones
- Reemplaza el approach implícito heredado del BACKLOG ("crear GCP project + OAuth client + scopes restricted").
- Compatible con ADR-006 (Inbox empezar con IMAP) — formaliza que IMAP queda como camino canónico para read, no transitorio.
- Compatible con ADR-014 (Authentik = source of truth) — los OAuth flows de Gmail/Drive/Calendar son resource OAuth, no login; el login federado con Google sigue por Authentik con scopes non-sensitive (openid + email + profile).
- Bloquea (hasta resolver Fase 2–4): activación productiva de cis-admin/documentos import + cis-inbox connect Gmail + envío en nombre del user.