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 UPDATE sin cláusula WHERE en tablas de alta criticidad
  • Detectar llamadas a funciones peligrosas como SLEEP() o PG_SLEEP()
  • Bloquear TRUNCATE y DROP TABLE en tiempo de ejecución, fuera de las migraciones
  • Acotar un DELETE o UPDATE sin WHERE a una tabla crítica con relation

Disponibilidad por plan

Las reglas custom están disponibles desde el plan BUILDER ($79/mes).

PlanReglas customLímite
FreeNo disponible—
Builder ($79/mes)✅ IncluidoHasta 5 reglas custom
Team ($249/mes)✅ IncluidoHasta 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

rule Opcional string

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.

node_type Requerido string

Tipo de nodo del AST que activa la condición. Ver tipos de nodo disponibles.

condition Opcional objeto

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.

message Opcional string

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_typeDescripciónDialecto
DeleteStmtSentencia DELETEpostgres / all
UpdateStmtSentencia UPDATEpostgres / all
SelectStmtSentencia SELECTpostgres / all
InsertStmtSentencia INSERTpostgres / all
DropStmtSentencia DROP (TABLE, DATABASE, INDEX, SCHEMA)postgres / all
TruncateStmtSentencia TRUNCATEpostgres
AlterTableStmtALTER TABLE (DROP COLUMN, RENAME, etc.)postgres
FuncCallLlamada 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

PredicadoValorSignificado
relation: "nombre"stringAcota 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

PredicadoValorSignificado
where_clause: nullnullLa query NO tiene cláusula WHERE
where_always_true: truetrueWHERE trivialmente verdadero (1=1, true)

Para SELECT

PredicadoValorSignificado
target_list: "*""*"La query usa SELECT *
has_limit: falsefalseLa query no incluye LIMIT

Para FuncCall

PredicadoValorSignificado
func_name: "nombre"stringNombre de la función a bloquear (case-insensitive)

Para DropStmt

PredicadoValorSignificado
object_type: "table""table", "database", "schema", "index"Tipo de objeto que se está eliminando

Para AlterTableStmt

PredicadoValorSignificado
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

CampoTipoDescripción
namestring (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.
descriptionstring (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_yamlstring 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 SELECT no 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 400 con el código INVALID_RULE_CONDITION y, en details, 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.