Integration Guide
Protect your databases in under 5 minutes. Zero credentials stored. Compatible with any PostgreSQL driver or ORM.
Prerequisites
- A Vetro account (sign up free)
- An API key (create one in Dashboard → API Keys)
- For TCP mode: Docker or any container runtime to deploy the proxy
- For HTTP mode: ability to make HTTP requests from your CI/CD pipeline
Zero credentials: Vetro never stores your database credentials. The TCP proxy runs in your infrastructure and forwards authentication transparently. Your credentials never leave your network.
Step 1: Create workspace & database
- Go to /register and create your account
- Navigate to Dashboard → Databases → Add Database
- Enter a name (e.g.
prod-users-db) and select the dialect (PostgreSQL, MySQL) - Copy the Database ID — you'll need it for configuration
- Go to Dashboard → API Keys and create a key — copy it securely
That's all you need from the Vetro dashboard. No connection strings, no credentials.
TCP Proxy — Production database protection
The TCP proxy intercepts every SQL query at the PostgreSQL wire protocol level. Deploy it as a sidecar or standalone container in your infrastructure. Your app connects to the proxy with the same credentials it uses for the database directly.
# docker-compose.yml
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.rds.amazonaws.com"
UPSTREAM_PG_PORT: "5432"
PROXY_PG_LISTEN_PORT: "5433"
# Remote DB (RDS): encrypt the proxy → database hop.
UPSTREAM_PG_SSLMODE: "require" # or "verify-full" + UPSTREAM_PG_SSLROOTCERT
app:
environment:
# Point your app to the proxy — same user:pass as before
DATABASE_URL: "postgres://user:pass@vetro-proxy:5433/mydb"
depends_on:
- vetro-proxy
Encrypting connections (TLS)
Proxy → database. When your database is remote (e.g. a managed RDS/Cloud SQL instance), encrypt the proxy → database hop with UPSTREAM_PG_SSLMODE. The proxy performs the PostgreSQL SSLRequest negotiation and tunnels the session through TLS:
# Encryption only (does not verify the server certificate)
UPSTREAM_PG_SSLMODE=require
# Encryption + verify the certificate chain and hostname
UPSTREAM_PG_SSLMODE=verify-full
UPSTREAM_PG_SSLROOTCERT=/etc/vetro/rds-ca.pem # omit to use public CA roots
# Optional: mutual TLS — present a client cert so the proxy authenticates to a
# database configured with `cert` auth (one service identity for the proxy)
UPSTREAM_PG_SSLCERT=/etc/vetro/proxy-client.crt
UPSTREAM_PG_SSLKEY=/etc/vetro/proxy-client.key
Client → proxy. By default the proxy declines client TLS, because it is meant to run inside the same trusted boundary as your app (sidecar, same host, or private subnet) where that hop never leaves your network. To encrypt it natively, set PROXY_TLS_MODE=require with a server certificate and key — the proxy answers the SSLRequest with 'S' and terminates TLS as the server:
PROXY_TLS_MODE=require
PROXY_TLS_CERT=/etc/vetro/server.crt
PROXY_TLS_KEY=/etc/vetro/server.key
Vetro recommends terminating client TLS at the proxy when:
- The app → proxy hop crosses an untrusted network (different host, VPC, or AZ) instead of a sidecar / private subnet.
- Your driver enforces
sslmode=requireand you don't want to run 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 in the same private subnet, leave it disabled — it isn't needed. This is server-side TLS (the client authenticates the proxy); the proxy relays auth end-to-end, so scram-sha-256, md5 and cloud token auth (IAM / Entra) work unchanged. SCRAM channel binding (SCRAM-SHA-256-PLUS) and per-end-client cert auth are not supported through a TLS-terminating proxy by design — both exist to detect a proxy in the middle. Inspecting SQL requires decrypting at the proxy, so opaque end-to-end TLS is incompatible with enforcement.
Frameworks & ORMs
Once the proxy is running, point your app to it. The only change is the host — same credentials, same database name:
| ORM / Driver | Configuration | Example |
|---|---|---|
| Prisma | DATABASE_URL in .env |
postgres://user:pass@vetro-proxy:5433/mydb |
| SQLAlchemy | create_engine(url) |
postgresql+psycopg2://user:pass@proxy:5433/mydb |
| Drizzle ORM | connectionString |
postgres://user:pass@proxy:5433/mydb |
| TypeORM | host + port in DataSource |
host: 'vetro-proxy', port: 5433 |
| ActiveRecord | DATABASE_URL |
postgres://user:pass@proxy:5433/mydb |
| Go (pgx/lib-pq) | sql.Open("postgres", url) |
postgres://user:pass@proxy:5433/mydb |
| Java (JDBC) | jdbc:postgresql://host:port/db |
jdbc:postgresql://vetro-proxy:5433/mydb |
| .NET (Npgsql) | Host=;Port=;Database= |
Host=vetro-proxy;Port=5433;Database=mydb |
HTTP API — CI/CD & pipeline integration
Evaluate SQL queries via REST without deploying a proxy. Ideal for pre-deploy checks, migration validation, and code review automation. Queries are analyzed but never executed against your database.
CI/CD pipelines
Add a step to your pipeline that sends each migration file to the Vetro API. If any query is blocked, the pipeline fails before reaching production.
# GitHub Actions
- name: Vetro SQL Check
run: |
for file in migrations/*.sql; do
RESULT=$(curl -s -X POST https://api.vetro.dev/api/v1/proxy/query \
-H "Authorization: Bearer ${{ secrets.VETRO_API_KEY }}" \
-H "Content-Type: application/json" \
-d "{\"query\": \"$(cat $file)\", \"database_id\": \"${{ vars.VETRO_DB_ID }}\", \"dialect\": \"postgres\"}")
DECISION=$(echo $RESULT | jq -r '.decision')
if [ "$DECISION" = "BLOCKED" ]; then
echo "::error file=$file::Blocked by $(echo $RESULT | jq -r '.rule_code')"
exit 1
fi
done
# GitLab CI
vetro-check:
stage: test
script:
- |
for file in migrations/*.sql; do
RESULT=$(curl -s -X POST https://api.vetro.dev/api/v1/proxy/query \
-H "Authorization: Bearer $VETRO_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"query\": \"$(cat $file)\", \"database_id\": \"$VETRO_DB_ID\", \"dialect\": \"postgres\"}")
DECISION=$(echo $RESULT | jq -r '.decision')
[ "$DECISION" = "BLOCKED" ] && echo "BLOCKED: $file" && exit 1
done
rules:
- changes: ["migrations/**/*.sql"]
Code examples
# Node.js — validate a query before execution
const result = await fetch('https://api.vetro.dev/api/v1/proxy/query', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VETRO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: sqlToValidate,
database_id: process.env.VETRO_DB_ID,
dialect: 'postgres',
}),
}).then(r => r.json());
if (result.decision === 'BLOCKED') {
throw new Error(`Blocked by ${result.rule_code}: ${result.ast_node_path}`);
}
# Python — pre-deploy migration check
import requests, sys, glob
for path in glob.glob("migrations/*.sql"):
with open(path) as f:
sql = f.read()
r = requests.post(
"https://api.vetro.dev/api/v1/proxy/query",
headers={"Authorization": f"Bearer {os.environ['VETRO_API_KEY']}"},
json={"query": sql, "database_id": os.environ["VETRO_DB_ID"], "dialect": "postgres"},
)
if r.json()["decision"] == "BLOCKED":
print(f"BLOCKED: {path} — {r.json()['rule_code']}")
sys.exit(1)
print("All migrations safe ✓")
Verify & monitor
Once integrated, verify the setup is working:
- TCP Proxy: Go to Dashboard → Databases and click "Verify connection". The proxy will report its status within seconds.
- HTTP API: Send a test query and check the response. A
200with"decision": "ALLOWED"confirms the integration works. - Dashboard: Navigate to the Queries page to see real-time evaluation events flowing in.
Enable Observe Mode during initial rollout — it logs all decisions without blocking anything, so you can validate your ruleset before enforcing it.