Reglas Custom.
Define condiciones AST determinísticas propias para bloquear queries específicas de tu dominio que las reglas estándar no cubren.
¿Qué son las reglas custom?
Las reglas estándar de Vericto cubren los patrones destructivos más comunes (DELETE sin WHERE, DROP TABLE, TRUNCATE, etc.). Las reglas custom permiten extender ese ruleset con condiciones específicas de tu negocio, definidas mediante una condición YAML sobre el árbol sintáctico AST de la query.
Como todas las reglas de Vericto, las custom son determinísticas: el mismo input produce siempre el mismo resultado. No hay modelos de ML, no hay varianza. Una query cumple la condición o no la cumple, sin ambigüedad.
Ejemplos de uso habitual:
- Bloquear
SELECT *(exfiltración masiva). Un SELECT no se puede acotar a una tabla; para proteger columnas concretas, etiquétalas en Columnas sensibles - Prevenir
UPDATEsin cláusula WHERE en tablas de alta criticidad - Detectar llamadas a funciones peligrosas como
SLEEP()oPG_SLEEP() - Bloquear
TRUNCATEyDROP TABLEen tiempo de ejecución, fuera de las migraciones - Acotar un
DELETEoUPDATEsin WHERE a una tabla crítica conrelation
Disponibilidad por plan
Las reglas custom están disponibles desde el plan BUILDER ($79/mes).
| Plan | Reglas custom | Límite |
|---|---|---|
| Free | No disponible | — |
| Builder ($79/mes) | ✅ Incluido | Hasta 5 reglas custom |
| Team ($249/mes) | ✅ Incluido | Hasta 20 reglas custom |
Si intentas crear una regla custom en el plan Free, la API responde con 403 Forbidden (código PLAN_UPGRADE_REQUIRED) y un link de upgrade. Al alcanzar el límite de tu plan, la misma respuesta lleva reason: "custom_rule_limit".
Esquema YAML
Cada regla custom lleva una condición YAML que define cuándo se dispara. El único campo obligatorio es node_type; rule es una etiqueta opcional.
rule: nombre-interno-de-la-regla # identificador libre
node_type: DeleteStmt # tipo de nodo AST a capturar
condition:
where_clause: null # solo si falta WHERE
Campos del YAML
Etiqueta interna de la regla, para quien lea el YAML. El motor la acepta pero no la usa: lo que identifica la regla en los logs y el audit trail es su código (CUSTOM-001) y el nombre que le diste al crearla. Usa kebab-case, ej: block-delete-on-payments.
Tipo de nodo del AST que activa la condición. Ver tipos de nodo disponibles.
Predicados que acotan el nodo. Son ocho, y todos van dentro de condition, nunca en el nivel superior: relation, where_clause, where_always_true, target_list, has_limit, func_name, object_type y alter_kind. Cuál aplica depende del node_type; el detalle y los valores válidos están en condiciones disponibles. Si omites condition, la regla se dispara para todo nodo del node_type indicado.
Reservado. El motor acepta el campo pero hoy no lo expone: la respuesta de bloqueo se construye con el código de la regla y la ruta del nodo AST. Para que un bloqueo se explique solo, pon el texto en el nombre de la regla, que sí aparece en el dashboard y en el audit trail.
Tipos de nodo AST
Los nodos del AST representan el tipo de sentencia o expresión SQL que Vericto detecta en el árbol sintáctico. El dialecto determina qué parser se usa (pg_query para PostgreSQL, sqlparser-rs para MySQL).
| node_type | Descripción | Dialecto |
|---|---|---|
DeleteStmt | Sentencia DELETE | postgres / all |
UpdateStmt | Sentencia UPDATE | postgres / all |
SelectStmt | Sentencia SELECT | postgres / all |
InsertStmt | Sentencia INSERT | postgres / all |
DropStmt | Sentencia DROP (TABLE, DATABASE, INDEX, SCHEMA) | postgres / all |
TruncateStmt | Sentencia TRUNCATE | postgres |
AlterTableStmt | ALTER TABLE (DROP COLUMN, RENAME, etc.) | postgres |
FuncCall | Llamada a función SQL (SLEEP, PG_SLEEP, etc.) | postgres / all |
Condiciones disponibles
Dentro de condition: puedes usar los siguientes predicados, agrupados por el tipo de nodo al que aplican. Cruzarlos importa: un predicado que el nodo nunca lleva haría que la regla no se disparara nunca, así que Vericto rechaza esa combinación al guardar y al previsualizar (error NEVER_FIRES, con la línea), tanto en el dashboard como en la API. Ejemplo: target_list: "*" con relation en un SelectStmt.
Para DELETE / UPDATE / INSERT / ALTER TABLE
| Predicado | Valor | Significado |
|---|---|---|
relation: "nombre" | string | Acota la regla a una tabla (case-insensitive; ignora el esquema, así public.payments coincide con payments). Solo funciona con DELETE, UPDATE, INSERT y ALTER TABLE. Con SelectStmt y FuncCall no funciona en ningún dialecto, y con TruncateStmt y DropStmt solo funciona en MySQL, Oracle y SQL Server, no en PostgreSQL. Vericto rechaza la regla si no puede dispararse nunca, y avisa si con el dialecto "Todos" solo se dispara en algunos. Para proteger las columnas de una tabla en lecturas, usa Columnas sensibles. |
Para DELETE / UPDATE / SELECT
| Predicado | Valor | Significado |
|---|---|---|
where_clause: null | null | La query NO tiene cláusula WHERE |
where_always_true: true | true | WHERE trivialmente verdadero (1=1, true) |
Para SELECT
| Predicado | Valor | Significado |
|---|---|---|
target_list: "*" | "*" | La query usa SELECT * |
has_limit: false | false | La query no incluye LIMIT |
Para FuncCall
| Predicado | Valor | Significado |
|---|---|---|
func_name: "nombre" | string | Nombre de la función a bloquear (case-insensitive) |
Para DropStmt
| Predicado | Valor | Significado |
|---|---|---|
object_type: "table" | "table", "database", "schema", "index" | Tipo de objeto que se está eliminando |
Para AlterTableStmt
| Predicado | Valor | Significado |
|---|---|---|
alter_kind: "drop_column" | "drop_column", "rename", "drop_constraint", "alter_column_type", "disable_trigger" | Subtipo del comando ALTER TABLE. Sin este predicado, la regla se dispara para cualquier ALTER TABLE. |
Ejemplos prácticos
1. Bloquear SELECT * (exfiltración masiva)
rule: block-select-star
node_type: SelectStmt
condition:
target_list: "*"
Bloquea queries como SELECT * FROM users o SELECT * FROM payments. Permite queries con columnas explícitas como SELECT id, email FROM users.
2. Bloquear UPDATE sin WHERE
rule: block-update-no-where
node_type: UpdateStmt
condition:
where_clause: null
Bloquea UPDATE users SET status = 'blocked' pero permite UPDATE users SET status = 'blocked' WHERE id = $1.
3. Detectar tautologías SQL injection
rule: block-or-tautology
node_type: DeleteStmt
condition:
where_always_true: true
Bloquea queries como DELETE FROM users WHERE id = 1 OR 1=1 detectando el branch OR trivialmente verdadero en el AST.
4. Bloquear llamadas a SLEEP (DoS) — una regla por dialecto
# MySQL
rule: block-sleep-calls
node_type: FuncCall
condition:
func_name: sleep
# PostgreSQL — la función tiene otro nombre
rule: block-pg-sleep-calls
node_type: FuncCall
condition:
func_name: pg_sleep
func_name compara el nombre exacto de la función (insensible a mayúsculas), no una familia. En MySQL la función se llama SLEEP y en PostgreSQL PG_SLEEP, así que una sola regla no cubre los dos: func_name: sleep deja pasar SELECT pg_sleep(10) sin avisar. Verificado contra el proxy. Si necesitas cubrir ambos, crea las dos reglas. La regla estándar VERICTO-070 sí cubre la familia completa, porque está implementada en el motor y no como predicado.
5. Bloquear SELECT sin LIMIT
rule: require-limit-on-select
node_type: SelectStmt
condition:
has_limit: false
message: "Las queries SELECT deben incluir cláusula LIMIT para evitar scans completos de tabla"
Útil para tablas muy grandes donde un full scan sin límite puede degradar el rendimiento significativamente.
6. Bloquear DROP de cualquier objeto
rule: block-all-drops
node_type: DropStmt
Sin condition:, la regla se dispara para cualquier DROP (TABLE, INDEX, SCHEMA). Útil en entornos de producción donde ningún DROP debe ejecutarse desde la aplicación.
7. Proteger una tabla específica
rule: protect-payments
node_type: DeleteStmt
condition:
relation: payments
where_clause: null
Con relation acotas la regla a una sola tabla: bloquea DELETE FROM payments sin WHERE, pero no afecta a otras tablas. Combina cualquier predicado con relation para limitar su alcance.
8. Bloquear DROP COLUMN
rule: block-drop-column
node_type: AlterTableStmt
condition:
alter_kind: drop_column
Bloquea ALTER TABLE users DROP COLUMN email (pérdida de datos irreversible) pero permite otros cambios como RENAME o ADD COLUMN.
Monitor primero, luego enforce
Una regla custom nueva empieza en modo monitor: el motor la evalúa y registra sus coincidencias en el audit trail, pero nunca bloquea. Cuando confíes en ella, ábrela en Reglas y usa Promover a enforce: la confirmación dice qué hará la regla en tu workspace (por ejemplo BLOQUEAR) y enlaza su última simulación, o te avisa si no la simulaste, si la simulación falló o si encontró flujos legítimos que se romperían. Puedes volver a monitor en cualquier momento.
Cómo funciona: el motor no tiene una acción por regla, resuelve cada coincidencia con el mapa severidad → acción de la política. Por eso una regla en monitor se envía al motor con la severidad más suave de tu política que no bloquea (Monitorear o, si no hay ninguna, Marcar). Sus eventos muestran esa severidad; la regla conserva la suya, que es la que se aplica al promoverla. Si todas las severidades de tu política bloquean, una regla en monitor queda en pausa (no se evalúa) y el dashboard lo indica.
Las reglas que ya existían antes de este cambio siguen en enforce, exactamente como estaban: ninguna regla en producción cambió de comportamiento. En la API, POST /rules/custom acepta "mode": "enforce" si quieres crear una regla aplicada desde el inicio, y PATCH /rules/custom/:rule_id con {"mode": "enforce"} la promueve; el cambio queda en el registro de auditoría como rule.mode.changed.
Para editar una regla, ábrela: verás la condición, su historial y su última simulación. Al editar, la condición se revisa mientras escribes y antes de guardar ves un diff lado a lado contra la versión guardada.
Previsualización antes de activar
Antes de guardar una regla custom, el dashboard ofrece una previsualización que evalúa el YAML contra las queries históricas del workspace de los últimos 7 días.
La previsualización muestra:
- Si el YAML es válido (
validation_result.valid: true/false) - Cuántas queries históricas hubiera bloqueado esta regla
- Un ejemplo de query que dispara la condición
- Lista de eventos del audit trail que coinciden
La previsualización es solo de lectura: no bloquea nada. Y crear la regla tampoco bloquea todavía: una regla nueva empieza en modo monitor y solo bloquea cuando la promueves a enforce.
Para un cambio con más en juego, el simulador de reglas reproduce el tráfico de tu workspace (hasta 30 días en Builder, 90 en Team y según contrato (hasta 180) en Enterprise; eliges la ventana en cada ejecución) con las reglas nuevas o editadas, las compara con las actuales y separa los flujos legítimos que se romperían. Desde el dashboard o desde CI, en los planes Builder y superiores.
API REST para reglas custom
Crear una regla custom
POST /api/v1/workspaces/:workspace_id/rules/custom
Authorization: Bearer <token>
{
"name": "Bloquear SELECT * (cualquier tabla)",
"description": "Obliga a listar columnas explícitas; evita exfiltración masiva",
"severity": "high",
"dialect": "postgres",
"ast_condition_yaml": "rule: block-select-star\nnode_type: SelectStmt\ncondition:\n target_list: \"*\"",
"mode": "monitor"
}
// Response 201
{
"rule_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"code": "CUSTOM-001",
"name": "Bloquear SELECT * (cualquier tabla)",
"severity": "high",
"is_active": true,
"mode": "monitor",
"created_at": "2025-06-20T10:00:00Z",
"validation_result": { "valid": true, "errors": [] }
}
// Promote it to enforce
PATCH /api/v1/workspaces/:workspace_id/rules/custom/:rule_id
{ "mode": "enforce" }
// A condition that can never fire: 400
{
"error": {
"code": "INVALID_RULE_CONDITION",
"details": [
{ "field": "ast_condition_yaml", "code": "NEVER_FIRES", "line": 4,
"params": { "key": "relation", "node_type": "SelectStmt" },
"suggestion": "sensitive_columns" }
]
}
}
Previsualizar antes de guardar
POST /api/v1/workspaces/:workspace_id/rules/custom/preview
Authorization: Bearer <token>
{
"ast_condition_yaml": "rule: block-select-star\nnode_type: SelectStmt\ncondition:\n target_list: \"*\"",
"dialect": "postgres",
"lookback_days": 7
}
// Response 200
{
"matching_queries": [...],
"total_matches": 3,
"validation_result": { "valid": true, "errors": [] },
"example_triggering_query": "SELECT * FROM users"
}
Campos requeridos
| Campo | Tipo | Descripción |
|---|---|---|
name | string (1–128 chars) | Nombre descriptivo de la regla. Es lo que se muestra en el dashboard y en el audit trail cuando la regla bloquea una query. |
description | string (máx. 1024 chars, opcional) | Justificación libre que aparece en la ficha de la regla |
severity | "critical" / "high" / "medium" / "low" / "informational" | Severidad. Cuando la regla está en enforce, la política de enforcement del workspace decide si bloquea, marca o solo observa. Cualquier otro valor devuelve 400. |
dialect | "postgres" / "mysql" / "oracle" / "mssql" / "all" | Dialecto SQL al que aplica la regla. "all" se almacena hoy como postgres. |
mode | "monitor" / "enforce" | Opcional. Sin él, la regla se crea en monitor: registra sus coincidencias y no bloquea hasta que la promuevas. |
ast_condition_yaml | string YAML (10–16384 chars) | Condición AST que define cuándo bloquear |
Limitaciones actuales
- Las condiciones YAML solo soportan los predicados documentados arriba: no se puede escribir expresiones arbitrarias sobre el AST todavía.
- Un
SELECTno se puede acotar a una tabla: el motor no registra la tabla de un SELECT. Para datos personales, etiqueta las columnas en Columnas sensibles. - Máximo 5 reglas custom en el plan Builder, 20 en Team e ilimitadas en Enterprise.
- La condición se valida en el servidor (y en el dashboard mientras escribes): un YAML mal formado, un predicado desconocido o una combinación que nunca se dispara devuelven
400con el códigoINVALID_RULE_CONDITIONy, endetails, cada problema con su código y su línea. - Las reglas custom no soportan CTE data-modifying todavía (ej:
WITH x AS (DELETE ...)). Las reglas estándar VERICTO-001 a VERICTO-020 sí las detectan.
Antes de bloquear con una regla custom, déjala en modo monitor y simúlala contra producción (Simular contra producción en el editor o en el detalle de la regla): la simulación reproduce tu tráfico real y separa los flujos legítimos que se romperían. Promuévela a enforce cuando el resultado sea limpio.
¿Tienes un caso de uso que las condiciones actuales no cubren? Escríbenos. Los casos reales de clientes guían el roadmap de nuevos predicados.