Saltar a contenido

Seguridad

El MCP público de Wiservet es un endpoint de lectura sobre data sensible (PII de tutores, datos médicos, financieros). La defensa en profundidad cubre 4 capas.

1. Capa pg role grants

El usuario PostgreSQL wiservet_mcp tiene únicamente:

GRANT USAGE ON SCHEMA public TO wiservet_mcp;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO wiservet_mcp;
-- Sin INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, EXECUTE.
ALTER ROLE wiservet_mcp SET statement_timeout = '5000ms';
ALTER ROLE wiservet_mcp SET default_transaction_read_only = on;

Cualquier intento de DML/DDL retorna error pg → 500 al cliente.

2. Capa regex sql_guard (pre-execute)

Antes de mandar la query al pool, un guardrail regex bloquea:

Patrón Razón
(?i)\b(insert|update|delete|truncate|drop|alter|create|grant|revoke|copy|comment|reindex|cluster|vacuum|analyze)\b DML/DDL
; no terminal Multi-statement
(?i)pg_(authid|shadow|read_file|read_binary_file|ls_dir|stat_file) Lectura filesystem / dump credenciales
(?i)\bdblink\b\|\bcopy\s+from\s+program\b Exfiltración / RCE
(?i)pg_sleep\|generate_series\(.*1e7 DoS pre-timeout

Bloqueo → response 400 con code: SQL_BLOCKED y razón.

3. Capa transaction READ ONLY + statement timeout

BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY;
SET LOCAL app.current_clinic_id = '<uuid-de-la-key>';
SET LOCAL statement_timeout = '5000ms';
SELECT ...;
COMMIT;

Si la query supera 5s → error 57014 → 500 al cliente con code: STATEMENT_TIMEOUT.

4. Capa RLS (Row Level Security)

Cada tabla con PII tiene una policy:

CREATE POLICY tenant_isolation ON <tabla>
  USING (clinic_id = current_setting('app.current_clinic_id')::uuid);

Aunque el SELECT no incluya WHERE clinic_id = ..., PostgreSQL filtra automáticamente. Imposible que una clínica vea data de otra vía MCP.

5. Cap server-side de filas

El handler MCP corta el resultset a 200 filas después del fetch (no en SQL):

// pseudocódigo
rows := query(...)
if len(rows) > 200 {
  rows = rows[:200]
  response.Truncated = true
}

Esto previene exfiltración masiva (200 rows × 1.000 calls/día = ~200K rows/día por key, manejable y trackeable).

6. Audit trail

Cada call (incluyendo bloqueados) escribe en mcp_usage_log:

Campo Ejemplo
tool_name query_database
mcp_api_key_id 9f...
clinic_id 849c...
request_payload {"sql":"SELECT * FROM visits..."} (truncado 4KB)
rows_returned 15
duration_ms 87
blocked_reason null o "DML_DETECTED"
created_at 2026-04-26T17:45:23Z

El DPO/clínica puede consultar este log vía endpoint /api/v1/mcp/usage (pendiente de implementar Sprint 6).

Reportar vulnerabilidades

Si encontrás una vulnerabilidad: security@wiservet.com (PGP opcional).

  • No abrir issue público en GitHub.
  • Programa de disclosure responsable: 90 días.
  • Reconocimiento (no monetario por ahora) en https://wiservet.com/security/hall-of-fame.