SQL migrations are the last place you want a surprise
LLM-generated migrations
Your agent wrote the migration. Did it add a WHERE clause? Did it scope the UPDATE? Did it avoid TRUNCATE? You don't know until it runs.
Manual review doesn't scale
At 10 deploys/day, nobody reads every migration carefully. One missed WHERE clause at 3am and you're doing an emergency restore.
Production is too late
Catching a DELETE FROM orders in staging is lucky. Catching it in CI is engineering.
What the Vericto CLI looks like in your pipeline
$ vericto check migrations/0042_cleanup.sql --dialect postgres Vericto — Deterministic SQL Firewall v1.4.2 Evaluating 4 queries against workspace ruleset... ✓ Line 3: ALTER TABLE users ADD COLUMN last_seen_at TIMESTAMPTZ ALLOWED ✓ Line 8: CREATE INDEX idx_users_last_seen ON users(last_seen_at) ALLOWED ✗ Line 14: DELETE FROM audit_logs BLOCKED Rule: VERICTO-001 — DELETE without WHERE clause AST node: DeleteStmt > WhereClause = NULL Severity: CRITICAL Fix: DELETE FROM audit_logs WHERE created_at < NOW() - INTERVAL '90 days' ✓ Line 19: UPDATE users SET last_seen_at = NOW() WHERE user_id = $1 ALLOWED Summary: 3 passed, 1 blocked Exit code: 1
Your pipeline fails at this step. The migration never reaches staging.
One step to add to your GitHub Actions workflow
name: CI
on: [push, pull_request]
jobs:
sql-safety:
name: SQL Safety Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vericto CLI
run: npm install -g @vericto/vericto-cli
- name: Check SQL migrations
env:
VERICTO_API_KEY: ${{ secrets.VERICTO_API_KEY }}
run: |
vericto check migrations/*.sql \
--dialect postgres \
--workspace ${{ vars.VERICTO_WORKSPACE_ID }}
The VERICTO_API_KEY is a workspace API key (vtro_...) with the ci_dryrun:execute scope. Create one in your Vericto dashboard under API Keys.
GitLab CI equivalent
sql-safety:
stage: validate
image: node:20-alpine
script:
- npm install -g @vericto/vericto-cli
- vericto check migrations/*.sql --dialect postgres
variables:
VERICTO_API_KEY: $VERICTO_API_KEY
VERICTO_WORKSPACE_ID: $VERICTO_WORKSPACE_ID
Every migration file, every query, every rule
The Vericto CLI sends each SQL statement in your file to the same AST engine that protects your production database. Same rules, same logic, zero drift between CI and production.
Supports
- .sql files
- Directories of .sql files (recursive)
- Piped SQL via stdin
- Multiple dialects: postgres, mysql, oracle, mssql
Reports
- Per-query decision (ALLOWED / BLOCKED)
- Rule code and AST node path
- Suggested fix per blocked query
- Exit code for CI integration
- JSON output for custom tooling (--format json)
Why 'shift left' SQL security matters
10x
cheaper to catch a bug in CI than in production (NIST, 2024)
4.2h
average time to recover from a destructive query incident (team of 3 seniors)
$0
cost of a blocked migration in CI vs potential data loss, restore time, and customer impact
The CLI and CI validation are available on every plan, with monthly quotas per plan (Free 1,000/mo, Builder 10,000/mo, Team and Enterprise with higher limits).
See plans and limits →Frequently asked questions
-
No. The dry-run mode evaluates SQL against the ruleset defined in your workspace, but never connects to any database. It's a pure static analysis step.
-
Yes, but without a workspace API key the CLI uses the default ruleset (CRITICAL rules only). For custom rules and your workspace configuration, you need a Team account.
-
Standard .sql files, directories (all .sql files recursively), and stdin. It handles multi-statement files, comments, and procedural blocks.
-
Yes, with
--fail-on. By default (--fail-on block) only BLOCKED findings fail the build; FLAGGED ones (like SELECT without LIMIT) are reported but pass. Use--fail-on flagto also fail on FLAGGED, or--fail-on anyto fail on any finding.