CreaRack-SL

Fuga de datos cross-tenant a Google AI Studio en perf-review / finops

Resumen

Severidad: 🔴 CRÍTICA (ALTA)
Detectado: Auditoría Suprema Etapa 3 (2026-06-11)
Arreglado: commit@6f8a46d

Endpoints /api/workspace/perf-review?ai=true y /api/workspace/finops?ai=true mandaban a Google AI Studio datos agregados de todas las organizaciones (clientes) con sus nombres reales + texto de queries — una fuga cross-tenant total de la plataforma.

El problema

El análisis de rendimiento (Performance Review) y finanzas (FinOps) agrega métricas de todas las organizaciones (tenant_usage, slow_queries) en un diccionario. Cuando el usuario activa ?ai=true para obtener análisis inteligente, el código pasaba ese diccionario íntegro a Google AI Studio sin anonimización:

# ANTES (vulnerable)
return AIOperations.detect_anomalies(data, tone=tone)
# → data contiene {"tenant_usage": [{"id": 1, "name": "ACME Corp", ...}, {"id": 2, "name": "Globex", ...}], ...}
# → enviado crudamente a Google

Un observador externo (Google o un atacante entre medio) podría:

  1. Ver qué empresas son clientes de CreaRack
  2. Ver sus patrones de consumo (racks, dispositivos)
  3. Ver SQL crudo de queries lentas (posibles secretos: contraseñas, API keys)

La solución

Función _anonymize_for_ai() en core/workspace_api.py:

  • Sustituye nombres reales por etiquetas opacas: "ACME Corp" → "org-1"
  • Preserva la distribución: Los conteos (racks, dispositivos) se mantienen idénticos
  • Redacta secretos en queries: Aplica redact_secrets() al texto SQL
  • Trabaja sobre una copia: La respuesta al cliente sigue mostrando nombres reales
def _anonymize_for_ai(data):
    """Quita identificadores de tenant y redacta secretos ANTES de enviar a un LLM externo."""
    d = copy.deepcopy(data)
    for row in d.get("tenant_usage", []) or []:
        if isinstance(row, dict) and "name" in row:
            row["name"] = f"org-{row.get('id', '?')}"
    for q in d.get("slow_queries", []) or []:
        if isinstance(q, dict) and q.get("query"):
            q["query"] = redact_secrets(str(q["query"]))
    return d

Se invoca antes de pasar a AIOperations:

def _ai_analyze_perf(data, tone="technical"):
    try:
        from core.services.ai_operations import AIOperations
        return AIOperations.detect_anomalies(_anonymize_for_ai(data), tone=tone)
    except Exception as e:
        logger.warning("workspace perf-review AI analysis failed: %s", redact_secrets(str(e)))
        return "AI analysis temporarily unavailable"

Impacto

  • Antes: Cualquiera con acceso a Google logs podía reidentificar clientes
  • Después: Google AI (y cualquier tercero) ve distribuciones numéricas sin identidad
  • El LLM sigue siendo capaz de analizar anomalías: La IA necesita números, no nombres

Tests

tests/api/test_config_ia.py:

  • test_anonymize_for_ai_strips_org_names_and_redacts_queries() — redacción correcta
  • test_anonymize_for_ai_does_not_mutate_original() — no muta la respuesta al cliente
  • test_anonymize_for_ai_tolerates_missing_keys() — robusto ante datos incompletos

Diferidos (motivo documentado)

Cambios de contrato / infra que no se cierran en esta tanda:

  • /media/ servido sin auth → rompería rendering frontend
  • global_exception_handler filtra str(exc) a ~509 endpoints → requiere barrido transversal T4
  • Llamada IA síncrona en request → T2 (síncrono→Huey), toca contrato

Véase también

  • [[feature—config-ia—auditoria-suprema-etapa-3]]
  • [[entity—core—service—anonymize-for-ai]]
  • [[concept—security—data-isolation]]
  • [[decision—20260611—agent-jwt-secret-dedicada]]
  • [[concept—saas—multi-tenancy]]