Introducción.

Todo lo que necesitas para integrar Vetro, configurar reglas, usar el API REST y entender el proxy AST.

Quick Start

Vetro protects your databases by evaluating every SQL query against security rules before it reaches your data. Two integration modes — choose based on your use case:

Option A: TCP Proxy (production databases)

Deploy a lightweight container in your infrastructure. Your app connects to the proxy instead of directly to the database — same credentials, different host.

# 1. Create account → Dashboard → Add Database → copy database_id
# 2. Deploy the proxy container
docker run -d --name vetro-proxy \
  -e VETRO_API_URL=https://api.vetro.dev \
  -e VETRO_API_KEY=vtro_your_key \
  -e VETRO_DATABASE_ID=your_database_id \
  -e UPSTREAM_PG_HOST=your-db-host \
  -e UPSTREAM_PG_PORT=5432 \
  -p 5433:5433 \
  ghcr.io/donkan168/vetro-proxy:latest

# 3. Update your app's connection string
DATABASE_URL=postgres://user:pass@<proxy-host-or-ip>:5433/mydb

Option B: HTTP API (CI/CD & pipelines)

Evaluate queries via REST — no proxy deployment needed. Ideal for validating migrations before deploy.

curl -X POST https://api.vetro.dev/api/v1/proxy/query \
  -H "Authorization: Bearer vtro_your_key" \
  -H "Content-Type: application/json" \
  -d '{"query": "DROP TABLE users", "database_id": "your_db_id", "dialect": "postgres"}'

# → { "decision": "BLOCKED", "rule_code": "VETRO-010" }

Zero stored credentials: Vetro never stores your database credentials or sends them to Vetro Cloud. The proxy runs in your infrastructure and relays the PostgreSQL authentication handshake to your database unchanged — credentials are verified by your database, not by Vetro.

Architecture

Vetro uses deterministic AST (Abstract Syntax Tree) parsing to evaluate SQL queries. No AI, no probabilistic models, no false positives — the same input always produces the same result.

How it works

Your App → Vetro Proxy (your infrastructure, port 5433)
               │
               ├─ Parse SQL into full AST (pg_query for Postgres, sqlparser-rs for MySQL)
               ├─ Evaluate AST against active security rules
               ├─ Resolve enforcement action (BLOCK / FLAG / MONITOR)
               │
               ├── SAFE? → Forward to your real database → relay response
               └── DESTRUCTIVE? → Return SQLSTATE 42501 (query never reaches DB)

Components

ComponentWhere it runsPurpose
vetro-proxyYour infrastructureTCP wire-protocol interception. Evaluates queries in-process using vetro-engine. Reports telemetry to Vetro API.
Vetro APIVetro CloudDashboard, rule management, telemetry storage, audit trail, HTTP query evaluation.
vetro-engineEmbedded in proxyRust AST parser + rule evaluator. P99 < 2ms. No network calls on the hot path.

TCP Proxy (PostgreSQL wire protocol)

The TCP proxy is a transparent interception layer. Any driver that uses the PostgreSQL wire protocol works without code changes. Your database credentials never leave your infrastructure.

Zero stored credentials

Vetro never stores your database credentials or transmits them to Vetro Cloud. The proxy runs in your infrastructure and relays the PostgreSQL authentication exchange — the Authentication / PasswordMessage flow that follows the StartupMessage — straight through to your database. Your app connects to the proxy with the same user/password it would use to connect directly, and the credentials are verified by your database, not by Vetro. With SCRAM-SHA-256 (the modern PostgreSQL default) the password is never transmitted in cleartext at all; the proxy only relays the challenge-response.

Setup

Step 1: Create a database entry in the Vetro dashboard (Settings → Databases). You'll receive a database_id.

Step 2: Deploy the proxy container with these environment variables:

# Required — connects the proxy to the Vetro control plane
VETRO_API_URL=https://api.vetro.dev
VETRO_API_KEY=vtro_xxxxxxxx            # from Dashboard → API Keys
VETRO_DATABASE_ID=40eed96f-...         # from Dashboard → Databases

# Required — your real database (stays in your network)
UPSTREAM_PG_HOST=my-rds.amazonaws.com
UPSTREAM_PG_PORT=5432

# Proxy listener
PROXY_PG_LISTEN_PORT=5433

Step 3: Update your application's connection string to point to the proxy:

# Before (direct to DB)
DATABASE_URL=postgres://user:pass@my-rds.amazonaws.com:5432/mydb

# After (via Vetro proxy — same credentials, different host)
DATABASE_URL=postgres://user:pass@<proxy-host-or-ip>:5433/mydb

Docker Compose example

services:
  vetro-proxy:
    image: ghcr.io/donkan168/vetro-proxy:latest
    ports:
      - "5433:5433"
    environment:
      VETRO_API_URL: "https://api.vetro.dev"
      VETRO_API_KEY: "${VETRO_API_KEY}"
      VETRO_DATABASE_ID: "${VETRO_DATABASE_ID}"
      UPSTREAM_PG_HOST: "your-db-host"
      UPSTREAM_PG_PORT: "5432"
      PROXY_PG_LISTEN_PORT: "5433"

Optional configuration

# Telemetry buffering (default: memory)
VETRO_TELEMETRY_BUFFER=memory          # or "disk" (survives restarts)
VETRO_TELEMETRY_DISK_PATH=/var/lib/vetro/spool
VETRO_TELEMETRY_BATCH_SIZE=100
VETRO_TELEMETRY_FLUSH_SECS=5

# Rule sync interval (default: 300s)
VETRO_RULES_SYNC_INTERVAL_SECS=300

# Upstream TLS — encrypt the proxy → database hop (default: disable)
UPSTREAM_PG_SSLMODE=require             # disable | require | verify-full
UPSTREAM_PG_SSLROOTCERT=/etc/vetro/rds-ca.pem   # CA bundle, only for verify-full
UPSTREAM_PG_SSLCERT=/etc/vetro/proxy-client.crt # optional: mutual TLS to the DB
UPSTREAM_PG_SSLKEY=/etc/vetro/proxy-client.key  # (cert auth, single service identity)

# Client TLS — terminate TLS on the app → proxy hop (default: disable)
PROXY_TLS_MODE=require                  # disable | require
PROXY_TLS_CERT=/etc/vetro/server.crt
PROXY_TLS_KEY=/etc/vetro/server.key

Wire protocol compatibility

Vetro operates as a fully transparent layer within the PostgreSQL wire protocol. The proxy automatically detects and evaluates SQL from both protocol modes — no configuration or driver changes required:

Protocol ModeDescriptionUsed by
Simple Query ('Q') The client sends the complete SQL statement as a single message. Vetro extracts and evaluates the full query text inline. psql, raw SQL execution, admin scripts, legacy applications
Extended Protocol ('P') The client sends a prepared statement with parameter placeholders ($1, $2...). Vetro evaluates the query structure at the Parse phase — before values are bound — ensuring deterministic protection regardless of runtime parameters. Prisma, SQLAlchemy, TypeORM, Drizzle, pg (node-postgres), Hibernate, ActiveRecord

The proxy handles both modes transparently within the same connection. All other protocol traffic (authentication handshake, query results, server notifications, COPY operations) passes through unmodified. There is no configuration needed to select between modes — Vetro inspects each message's type byte and only intercepts SQL-carrying frames.

What Vetro evaluates (not just the obvious cases)

Because Vetro walks the full PostgreSQL AST (via libpg_query — the same parser the database uses), it catches destructive statements that surface-level / regex inspection misses:

  • Multi-statement queries: every statement in a batch is parsed and evaluated, and the highest-severity violation wins. A destructive tail cannot hide behind a benign head — SELECT 1; DROP TABLE users; is blocked on the DROP.
  • Data-modifying CTEs & subqueries: destructive operations nested inside WITH clauses or subqueries are detected — e.g. WITH x AS (DELETE FROM users RETURNING id) SELECT * FROM x.
  • Parameterized statements: prepared statements are evaluated at Parse time on the query structure, so protection is deterministic regardless of the bound values.
  • Injection tautologies: always-true predicates such as OR 1=1 in a WHERE clause are flagged as SQL-injection patterns.
  • Multi-dialect: PostgreSQL, MySQL, Oracle and SQL Server are parsed with dialect-aware rules (e.g. MySQL DELETE … LIMIT is valid; Postgres has no such form).

TLS / Encryption

Proxy → database (upstream): set UPSTREAM_PG_SSLMODE to encrypt the hop to your database. Use require for encryption only, or verify-full to also validate the server certificate chain and hostname (provide the CA bundle via UPSTREAM_PG_SSLROOTCERT, or omit it to use the public CA roots). This is recommended whenever the database is remote (e.g. a managed RDS instance): the proxy performs the PostgreSQL SSLRequest negotiation and tunnels the session through TLS. To authenticate to a database that uses cert auth, set UPSTREAM_PG_SSLCERT + UPSTREAM_PG_SSLKEY — the proxy then presents that client certificate (one service identity) for mutual TLS.

Client → proxy: by default the proxy declines client TLS because it is designed to run inside the same trusted boundary as your application (sidecar, same host, or private subnet), where that hop never leaves your network. To encrypt it, set PROXY_TLS_MODE=require with PROXY_TLS_CERT + PROXY_TLS_KEY; the proxy answers the SSLRequest with 'S' and terminates TLS as the server. This is server-side TLS (the client authenticates the proxy).

When Vetro recommends terminating client TLS at the proxy:

  • The app → proxy hop crosses an untrusted network (different host, VPC, or availability zone) rather than a sidecar / private subnet.
  • Your driver enforces sslmode=require (or stricter) and you cannot or prefer not to put a TLS-terminating load balancer (AWS NLB, HAProxy) in front.
  • A compliance baseline mandates encryption in transit on every hop.

If the proxy already runs as a sidecar or inside the same private subnet, this is unnecessary — leave it disabled. Inspecting SQL requires the connection to be decrypted at the proxy, so end-to-end TLS that the proxy cannot read is incompatible with query enforcement by design.

Authentication compatibility: the proxy relays the PostgreSQL authentication exchange end-to-end, so the common methods work unchanged — scram-sha-256, md5, cleartext password, and cloud token auth (AWS IAM, GCP IAM, Azure Entra). Two methods are not supported when the proxy terminates client TLS, by design: SCRAM channel binding (SCRAM-SHA-256-PLUS) and per-end-client cert auth — both bind authentication to the TLS channel specifically to detect a proxy in the middle.

Native error handling: A blocked query returns a standard PostgreSQL error with SQLSTATE 42501 (insufficient_privilege). Your application receives it through the same error handling path as any other database error — no Vetro-specific SDK, wrapper, or error parsing required.

Dashboard-managed settings (hot-reload)

The following proxy settings can be configured from the Vetro dashboard (Databases → Advanced Settings) and are applied automatically without restarting the proxy:

SettingDefaultDescription
Rules Sync Interval300sHow often the proxy polls for rule updates (min: 30s)
Telemetry Batch Size100Max events per telemetry POST
Telemetry Flush Interval5sHow often the telemetry buffer is flushed
Telemetry Memory Capacity10,000Max events in the ring buffer before oldest are dropped

The proxy reads these settings from the /sync/rules response on every poll cycle. Changes take effect within one sync interval — no proxy restart required.

Note: TLS to your database (UPSTREAM_PG_SSLMODE and the CA certificate) is configured via environment variables in the proxy container, not the dashboard — it requires mounting a certificate file in your infrastructure.

Telemetry buffering: Memory vs Disk

The proxy buffers evaluation events before sending them to the Vetro API. Two strategies are available:

ModeBehaviorBest for
memory (default) Events are held in a fixed-size ring buffer (default: 10,000 events). If the API is unreachable, the oldest events are dropped when the buffer fills. Lost on container restart. Low-latency environments where occasional event loss during API outages is acceptable. Zero disk I/O overhead.
disk Events are written to an append-only spool on disk. Survives container restarts and long API outages. The reporter reads and deletes from the spool after successful delivery. Compliance-critical environments (SOC2, ISO 27001) where no event must be lost. Requires a persistent volume mount.
# Memory mode (default — zero config)
VETRO_TELEMETRY_BUFFER=memory
VETRO_TELEMETRY_MEMORY_CAPACITY=10000   # max events before ring drops oldest

# Disk mode (persistent — requires volume)
VETRO_TELEMETRY_BUFFER=disk
VETRO_TELEMETRY_DISK_PATH=/var/lib/vetro/spool

Important: Telemetry is always non-blocking. Regardless of buffer mode, the SQL evaluation path is never delayed by telemetry I/O. Events are pushed to the buffer asynchronously after the decision is made.

Resource recommendations

The proxy is designed to be lightweight. The AST engine is compiled to native code and evaluates queries in-process — no network calls on the hot path.

WorkloadCPUMemoryDiskQueries/sec
Small (dev, staging) 0.25 vCPU 64 MB None (memory buffer) Up to 1,000 q/s
Medium (production) 0.5 vCPU 128 MB 100 MB (if disk buffer) Up to 10,000 q/s
High (high-throughput) 1 vCPU 256 MB 500 MB (disk buffer) 50,000+ q/s

Key factors: CPU scales with query complexity (multi-JOIN queries take more AST nodes to parse). Memory scales with connection count (each TCP session holds ~4 KB state). Disk is only needed for disk-mode telemetry buffering.

Latency target: P99 < 2ms for rule evaluation. The proxy adds negligible overhead compared to network round-trip to your database.

HTTP API (alternative — no proxy deployment)

For CI/CD pipelines and pre-deploy checks, you can evaluate queries via REST without deploying the TCP proxy:

curl -X POST https://api.vetro.dev/api/v1/proxy/query \
  -H "Authorization: Bearer vtro_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "DELETE FROM users WHERE id = 1",
    "database_id": "40eed96f-...",
    "dialect": "postgres"
  }'

# Response: { "decision": "BLOCKED", "rule_code": "VETRO-001", ... }

Decisions & Actions

Vetro separates severity (how dangerous: Critical / High / Medium / Low / Informational) from enforcement action (what to do: BLOCK / FLAG / MONITOR). The action is resolved by the workspace's enforcement policy.

DecisionBehaviorHTTP status
ALLOWED Query does not violate any active rule. TCP proxy forwards to upstream. HTTP API returns the decision. 200
FLAGGED Query violates a rule with action FLAG: forwarded to upstream but generates telemetry + alert. Example: SELECT without LIMIT (Medium → FLAG). 200
MONITORED Query violates a rule with action MONITOR: forwarded and logged, no alert. Example: INSERT without explicit columns (Low → MONITOR). 200
BLOCKED Query violates a rule with action BLOCK. TCP proxy returns SQLSTATE 42501. Response includes rule_code, ast_node_path, and suggested_safe_query. 403
PARSE_ERROR Query has invalid syntax. Default: fail-open (forward + log). Configurable to fail-closed (BLOCK) per workspace. 200 / 403

Default enforcement policy

SeverityDefault ActionExample rules
Critical / HighBLOCKVETRO-001 (DELETE without WHERE), VETRO-010 (DROP TABLE)
MediumFLAGVETRO-050 (SELECT without LIMIT)
Low / InformationalMONITORVETRO-060 (INSERT without columns)
Parse errorAllow + reportInvalid SQL syntax

Observe mode (dry-run): When enabled, all BLOCK actions are downgraded to FLAG — nothing is blocked. Use this to validate your ruleset impact before enabling enforcement in production.

Query telemetry & privacy

For every query it evaluates, the proxy emits a telemetry event to the Vetro API (status, severity, rule code, latency, and the query text). This powers the dashboard, metrics, and audit trail. Your database credentials are never part of telemetry — but the query text can contain sensitive values in its literals (e.g. WHERE email = 'alice@acme.com').

The query telemetry mode is configurable per workspace (Dashboard → Settings → Privacidad de la telemetría):

ModeWhat is reportedTrade-off
raw (default) The full query text, exactly as executed. Maximum forensic detail in the audit trail. Literal values (potential PII) leave your network and are stored by Vetro.
sanitized The query with literals normalized to placeholders ($1, $2, …) before it leaves the proxy. No user data leaves your network. You lose the exact values, but keep the query structure for metrics and grouping.

Enforcement is unaffected. Rule evaluation always runs against the full query in-process; sanitization only changes what is reported. Switching to sanitized never weakens blocking — it only redacts telemetry.

Sanitization is performed structurally on the parsed AST (for PostgreSQL via libpg_query's query normalization), so it is deterministic and does not rely on regular expressions. Example:

# raw
DELETE FROM users WHERE email = 'alice@acme.com' AND tenant_id = 42

# sanitized (what Vetro reports)
DELETE FROM users WHERE email = $1 AND tenant_id = $2

Recommendation: use sanitized for workspaces handling regulated or personal data (GDPR, HIPAA, Ley 1581 de 2012), and raw when full forensic fidelity in the audit trail is the priority and queries do not embed sensitive literals.

Authentication & HTTP Integration

All programmatic access to Vetro uses API keys. Create an API key from the dashboard (API Keys page) and include it in every request as a Bearer token.

Authorization: Bearer vtro_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API keys are scoped to a workspace and inherit the workspace's plan limits. You can create multiple keys with different names for different environments (CI, staging, production).

Query evaluation endpoint

POST /api/v1/proxy/query API Key

Evaluate a SQL query against the active ruleset. Returns a decision (ALLOWED, BLOCKED, FLAGGED) without executing the query against any database. Ideal for CI/CD pipelines, pre-deploy checks, and code review automation.

Request

{
  "query": "DELETE FROM users WHERE active = false",
  "database_id": "40eed96f-5602-41ef-807c-a4e821a8f4fa",
  "dialect": "postgres"
}

Response (BLOCKED)

{
  "decision": "BLOCKED",
  "rule_code": "VETRO-001",
  "severity": "critical",
  "ast_node_path": "DeleteStmt > WhereClause = NULL",
  "suggested_safe_query": "DELETE FROM users WHERE active = false AND id = $1",
  "event_id": "uuid"
}

Response (ALLOWED)

{
  "decision": "ALLOWED",
  "rule_code": null,
  "severity": null,
  "latency_ms": 0.42,
  "event_id": "uuid"
}

Integration examples

curl -X POST https://api.vetro.dev/api/v1/proxy/query \
  -H "Authorization: Bearer vtro_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "DROP TABLE users",
    "database_id": "40eed96f-5602-41ef-807c-a4e821a8f4fa",
    "dialect": "postgres"
  }'

# Response (403):
# { "decision": "BLOCKED", "rule_code": "VETRO-010", ... }

Response codes

HTTP StatusMeaning
200Query evaluated — decision is ALLOWED, FLAGGED, or MONITORED
400Query has syntax errors (PARSE_ERROR) — cannot be evaluated
403Query is BLOCKED — violates an active rule
401Invalid or missing API key
429Rate limit exceeded — retry after the indicated interval

Security Rules

CodeDetectionSeverityDialects
VETRO-001DELETE sin WHERECRITICALTodos
VETRO-003DELETE con WHERE siempre verdadero (1=1)CRITICALTodos
VETRO-010DROP TABLE / DATABASECRITICALTodos
VETRO-011TRUNCATE TABLECRITICALTodos
VETRO-012DROP SCHEMACRITICALTodos
VETRO-030UPDATE sin WHERE (tablas principales)CRITICALTodos
VETRO-042UPDATE sin WHERECRITICALTodos
VETRO-002DELETE con LIMIT 0HIGHMySQL, SQLite
VETRO-013DROP INDEX sin IF EXISTSHIGHTodos
VETRO-015ALTER TABLE DROP COLUMNHIGHTodos
VETRO-016ALTER TABLE RENAMEHIGHTodos
VETRO-031UPDATE sin WHERE en CTEHIGHPostgres
VETRO-033DELETE sin WHERE en subquery/CTEHIGHTodos
VETRO-040INSERT INTO … SELECT sin filtroHIGHTodos
VETRO-070SLEEP() / PG_SLEEP()HIGHTodos
VETRO-090SQL injection — tautología OR en WHERE (OR 1=1)CRITICALTodos
VETRO-050SELECT sin LIMITMEDIUMTodos
VETRO-051SELECT * sin WHEREMEDIUMTodos
VETRO-060INSERT sin columnas explícitasMEDIUMTodos
VETRO-061INSERT batch > 10k filasMEDIUMTodos
VETRO-PARSE-ERRORSintaxis SQL inválida (fail-closed)CRITICALTodos

See the complete list in the AST Rules Reference.

Error Codes

HTTPCodeDescription
400VALIDATION_ERRORInvalid or missing parameters
401UNAUTHORIZEDInvalid, expired, or missing API key / token
403FORBIDDENInsufficient permissions for the operation
403PLAN_UPGRADE_REQUIREDFeature requires a higher plan
403QUERY_BLOCKEDQuery blocked by AST security rule
404NOT_FOUNDResource not found or purged by retention policy
409CONFLICTDuplicate email or name
423ACCOUNT_LOCKEDAccount locked after failed attempts (15 min)
429RATE_LIMIT_EXCEEDEDToo many requests. See retry_after_seconds in response

Plans & Limits

FeatureFreeBuilderTeamEnterprise
Queries / month500K5MUnlimitedUnlimited
Databases1310Unlimited
Standard rules (VETRO-*)
Custom rules (YAML)520Unlimited
Audit trail retention30 days90 daysConfigurable
Audit trail export (CSV/JSON)
Slack / webhook alerts
CI/CD integration (HTTP API)
SOC2 / ISO 27001 compliance
SSO (SAML/OIDC)
Dedicated deployment
Uptime SLA99.5%99.9%Custom