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.