{"slug": "show-hn-vericto-check-ai-agents-sql-before-postgres-or-mysql-runs-it", "title": "Show HN: Vericto – Check AI agents' SQL before Postgres or MySQL runs it", "summary": "Vericto released vericto-proxy 4.15.2, a source-available SQL firewall that sits between clients and PostgreSQL or MySQL on the database's own wire protocol and blocks statements matching configured rules before they execute. The proxy parses each query with the vericto-engine AST parser and returns a native error — SQLSTATE 42501 on PostgreSQL or ERROR 1142 on MySQL — so applications only change the host and port in their connection string; a demo shows a DELETE without a WHERE clause blocked with error VERICTO-001 while a DELETE with a WHERE clause runs. The software is licensed under FSL-1.1-ALv2, which permits free self-hosting including production use at for-profit companies but bars competing commercial products, with each release converting to Apache-2.0 two years after release; one instance fronts one database and speaks one protocol set at deploy time via VERICTO_WIRE_PROTOCOL, and Oracle, SQL Server, Snowflake and SQLite are evaluated through the HTTP API, CLI or MCP instead.", "body_md": "Stops destructive SQL before it reaches your database, whether it comes from AI agents, scripts or people, with no code changes.\n\nAI agents, scripts and people all run SQL against production with real credentials.\nNobody reads each statement before it runs, and a database role decides what a user\nmay touch, not whether this particular statement should run. One `DELETE` without a\n`WHERE` empties a table. A `SELECT` of the wrong columns copies emails or card\nnumbers to wherever the result goes.\n\n`vericto-proxy` sits between your clients and the database, on the database's own\nwire protocol. It parses every query with the\n[vericto-engine](https://github.com/vericto/vericto-engine) AST parser, checks it\nagainst your rules, and either forwards it to the real database or blocks it with a\nnormal SQL error. Your application only changes the host and port in its connection\nstring.\n\n**License.** Source-available under the\n[FSL-1.1-ALv2](https://github.com/vericto/vericto-proxy/blob/main/LICENSING.md). You can self-host it and use it for free, including\nin production at a for-profit company. What it does not allow is a Competing Use:\nmaking it available to others in a commercial product or service that substitutes\nfor it or offers substantially similar functionality, such as a SQL firewall\nservice built on it. Each release also becomes Apache-2.0 two years after Vericto\nmakes it available. [LICENSING.md](https://github.com/vericto/vericto-proxy/blob/main/LICENSING.md) answers the common questions.\n\nOne proxy instance fronts one database and speaks exactly one wire protocol,\nchosen at deploy time with `VERICTO_WIRE_PROTOCOL`:\n\n| `VERICTO_WIRE_PROTOCOL` | Databases | Block response | \n|---|---|---|\n| `postgres` (default) | PostgreSQL | native `ErrorResponse`` SQLSTATE 42501` | \n| `mysql` | MySQL | native `ERR_Packet`` ERROR 1142` | \n\nEverything else — configuration, evaluation, telemetry — is **the same across\nengines**. Only the value above and (optionally) the default ports change.\n\nNo AI, no stochastic heuristics — the same input always produces the same result.\n\n**Other dialects** (Oracle, SQL Server, Snowflake, SQLite): evaluate via the\n[HTTP API](https://vericto.com/integration-guide#http-setup), the CLI or MCP;\nthere is no wire proxy for them. Snowflake has no TCP wire protocol at all, so\n`VERICTO_WIRE_PROTOCOL=snowflake` stops the proxy at startup with an error.\nSQLite is a library linked into the application, with no wire protocol either:\nits SQL is checked in the application, before it runs.\n\nAll you need is Docker: no account, no API key, no build. This starts a throwaway\nPostgres, puts the proxy in front of it with the built-in ruleset, and sends a few\nqueries through it with the `psql` that ships in the Postgres image. The password\n`demo` is only for this disposable database.\n\n```\ndocker network create vericto-demo\ndocker run -d --name vericto-demo-db --network vericto-demo \\\n  -e POSTGRES_PASSWORD=demo postgres:17\ndocker run -d --name vericto-demo-proxy --network vericto-demo \\\n  -e UPSTREAM_HOST=vericto-demo-db vericto/vericto-proxy:4.15.2\nuntil docker exec vericto-demo-db pg_isready -q -h localhost -U postgres; do sleep 1; done\n\ndocker run --rm --network vericto-demo postgres:17 \\\n  psql postgresql://postgres:demo@vericto-demo-proxy:5433/postgres \\\n  -c \"CREATE TABLE users (id int PRIMARY KEY, email text)\" \\\n  -c \"INSERT INTO users VALUES (1, 'ana@example.com'), (42, 'bo@example.com')\" \\\n  -c \"DELETE FROM users\" \\\n  -c \"DELETE FROM users WHERE id = 42\" \\\n  -c \"SELECT id, email FROM users\"\n```\n\nThe image comes from Docker Hub; `ghcr.io/vericto/vericto-proxy:4.15.2` serves the\nsame digest (see [Docker](#docker)).\n\nThe `DELETE` without a `WHERE` never reaches the database; the one with a `WHERE`\ndoes:\n\n```\nCREATE TABLE\nINSERT 0 2\nERROR:  Vericto blocked this query [VERICTO-001] — AST node: DeleteStmt > WhereClause = NULL. Suggestion: DELETE FROM users WHERE id = $1\nDELETE 1\n id |      email\n----+-----------------\n  1 | ana@example.com\n(1 row)\n```\n\n`docker logs vericto-demo-proxy` shows the decision. To put the proxy in front of\nyour own database, see [Docker](#docker) and\n[Connection strings](#connection-strings). Clean up with:\n\n```\ndocker rm -f vericto-demo-db vericto-demo-proxy && docker network rm vericto-demo\nYour app / ORM\n     │\n     │  SQL wire protocol (proxy listen port, default 5433)\n     ▼\n vericto-proxy   ──── vericto-engine (lib) ────►  ALLOWED / BLOCKED\n     │\n     │  (if ALLOWED) forwards query\n     ▼\n Real database upstream\n```\n\nPoint your `DATABASE_URL` host at `vericto-proxy` instead of your real database.\nNo code changes required — your ORM/driver is unaware of the proxy.\n\nWith the built-in ruleset, these never reach the database:\n\n- `DELETE` /`UPDATE` without`WHERE`\n- `DROP TABLE` ,`DROP SCHEMA` ,`TRUNCATE`\n- OR-tautology SQL injection (`WHERE id = $1 OR 1=1` )\n- `ALTER TABLE DROP COLUMN` /`RENAME`\n- … [full rule list →](https://vericto.com/rules-reference)\n\nLower-severity findings — `SELECT *` without `WHERE`, `SELECT` without `LIMIT`,\n`INSERT` without an explicit column list — are forwarded and recorded rather than\nblocked. With the control-plane link on, the workspace policy decides the action\nfor each severity.\n\nA blocked query is returned as a **native error** for the active protocol (see\nthe table above), so no special handling is needed in your application.\n\nColumns can be tagged as sensitive per database in the Vericto dashboard (Team and\nEnterprise plans). The tags reach the proxy with the ruleset (`/sync/rules`, so the\ncontrol-plane link and `VERICTO_DATABASE_ID` are required) and are enforced on the\nwire, before the database sees the query. When a query reads several tagged columns\nthe strictest policy wins (`block` > `mask` > `flag`). **The proxy enforces:** unlike\nthe Validation API or the CLI, which only advise, what the proxy decides is what the\ndatabase receives.\n\n| Policy | Postgres | MySQL | \n|---|---|---|\n| `block` | The query never reaches the database: `ErrorResponse` , SQLSTATE 42501 | Never reaches the database: `ERR_Packet` , ERROR 1142 | \n| `flag` | Forwarded as sent, recorded as FLAGGED | Same | \n| `mask` | The proxy forwards a **rewritten** query in which the column is masked (`full` ,`last4` ,`email` or`hash` ) under its own name, and never the original. The client receives masked values. | Same: the proxy forwards the rewritten query in the same `COM_QUERY` or`COM_STMT_PREPARE` , and never the original. | \n\nIf a rewrite does not hold together, the proxy blocks with `VERICTO-085` rather than\nforward the original. `SELECT *` over a table with a masked or blocked column is\nblocked; list the columns. The full behaviour (both protocols, prepared statements,\nfail-safe cases, parse errors, telemetry, known limits) is in\n[docs/SENSITIVE_COLUMNS.md](https://github.com/vericto/vericto-proxy/blob/main/docs/SENSITIVE_COLUMNS.md).\n\nAn allowlist per database user (Team and Enterprise plans), set in the Vericto\ndashboard: which tables and columns that user may read, and which it may write.\nEverything else is denied (`VERICTO-087`). The policies reach the proxy with the\nruleset (`/sync/rules`, so the control-plane link and `VERICTO_DATABASE_ID` are\nrequired), keyed by database user, with an optional `\"*\"` default for every user\nnot listed.\n\nThe identity of a session is the database user it authenticated as, so give each\nagent its own database user and a policy for that user. Under a policy, every\nreference counts (`WHERE`, `JOIN`, subqueries, `RETURNING`, not only what the query\nreturns), DDL is always denied, and the identity cannot be changed from inside the\nsession (`SET ROLE`, `USE`, `COM_CHANGE_USER` and the like are denied). A policy in\n`observe` mode records what it would deny and forwards the query.\n\nThe full behaviour (name resolution, writes and locks, policy changes on open\nsessions, telemetry, known limits) is in [docs/AGENT_ACCESS.md](https://github.com/vericto/vericto-proxy/blob/main/docs/AGENT_ACCESS.md).\n\nAll variables are **dialect-agnostic** — the same names apply to every engine.\n\n| Variable | Description | \n|---|---|\n| `UPSTREAM_HOST` | Hostname of the real database to proxy to | \n\n| Variable | Default | Description | \n|---|---|---|\n| `UPSTREAM_PORT` | protocol default¹ | Port of the upstream database | \n| `PROXY_LISTEN_PORT` | protocol default¹ | Port the proxy listens on | \n\n¹ Defaults follow `VERICTO_WIRE_PROTOCOL`: **Postgres** `5432` upstream / `5433`\nlisten; **MySQL** `3306` upstream / `3307` listen.\n\nThe rest is optional and documented in\n[docs/CONFIGURATION.md](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md):\n\n- **[Control-plane link](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#control-plane-link-telemetry--rule-sync)** (`VERICTO_API_URL` ,`VERICTO_API_KEY` ,`VERICTO_DATABASE_ID` , all three\ntogether): reports decisions to the Vericto API and syncs the workspace's rules\nand policy. Without it the proxy still protects with the built-in ruleset, and\nreports nothing. Also covers the telemetry buffer,[over the monthly quota](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#over-the-monthly-quota) ,[disk spool size](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#disk-spool-size) and[several proxies on one volume](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#several-proxies-on-one-volume) .\n- **[TLS](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#tls-optional)** on each hop, client→proxy and\nproxy→database. On MySQL, TLS must be on both hops or neither.\n- **[Query size limit](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#query-size-limit)** (`VERICTO_MAX_QUERY_BYTES` , 10 MiB by default).\n- **[Business policy rules](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#business-policy-rules-preview-off-by-default)** (`VERICTO_POLICY_RULES` ,`off` by default): a preview, off unless you turn it\non; with it off the proxy behaves exactly as 4.14.3.\n- **[Health check](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#health-check-optional)** for load\nbalancers (`VERICTO_HEALTHZ_PORT` ).\n- **[Observability](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#observability)** : logs, and[metrics](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#metrics) as Prometheus or CloudWatch EMF.\n- **[Engine-specific notes](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#engine-specific-notes)** ,\nincluding[MySQL details](https://github.com/vericto/vericto-proxy/blob/main/docs/CONFIGURATION.md#mysql-details) (TLS, protocol\ncompression).\n\nTo run from source and run the tests, see\n[CONTRIBUTING.md](https://github.com/vericto/vericto-proxy/blob/main/CONTRIBUTING.md#run-from-source).\n\nRelease images are published on Docker Hub as\n[`vericto/vericto-proxy`](https://hub.docker.com/r/vericto/vericto-proxy) (from\n4.14.1 on) and at `ghcr.io/vericto/vericto-proxy`, for `linux/amd64` and\n`linux/arm64`, and pull without credentials. The release workflow copies the\nmulti-arch index it pushed to GHCR, so both registries serve the same digest under\nthe same tags (`<version>`, `<major>.<minor>`, `<major>` and `latest`): pull from\nwhichever your environment allows.\n\n```\n# Postgres. On Linux, add --add-host=host.docker.internal:host-gateway\ndocker run --rm -e UPSTREAM_HOST=host.docker.internal -p 5433:5433 \\\n  vericto/vericto-proxy:4.15.2\n\n# MySQL\ndocker run --rm \\\n  -e VERICTO_WIRE_PROTOCOL=mysql \\\n  -e UPSTREAM_HOST=host.docker.internal \\\n  -p 3307:3307 \\\n  vericto/vericto-proxy:4.15.2\n\n# The same image from GHCR\ndocker pull ghcr.io/vericto/vericto-proxy:4.15.2\n```\n\nImages released from now on carry a build provenance attestation (Sigstore, GitHub Actions) that ties their digest to this repository, the release workflow and the commit. Verify one before you deploy it, and deploy by digest:\n\n```\ngh attestation verify oci://ghcr.io/vericto/vericto-proxy:<version> --repo vericto/vericto-proxy\n\n# Docker Hub (4.14.1 and later): the same digest, attested under its Docker Hub name\ngh attestation verify oci://docker.io/vericto/vericto-proxy:<version> --repo vericto/vericto-proxy\n```\n\nEach platform image (4.15.2 and later) also carries a signed SPDX SBOM attestation,\nmade for the platform manifest's digest (`docker buildx imagetools inspect` lists\nboth):\n\n```\ngh attestation verify oci://ghcr.io/vericto/vericto-proxy@<platform digest> \\\n  --repo vericto/vericto-proxy --predicate-type https://spdx.dev/Document/v2.3\n```\n\nThe runtime base is [Chainguard `glibc-dynamic`](https://images.chainguard.dev/directory/image/glibc-dynamic/overview)\n(4.15.2 and later; `debian:bookworm-slim` before): glibc, libgcc and the CA bundle,\n**no shell and no package manager**. The proxy runs as `65532:65532` (`nonroot`; it\nwas `1001:1001` up to 4.15.1, so `chown` a volume an older image wrote, or pass\n`--user 1001:1001`). Health checks call the binary instead of a shell, in exec form:\n`[\"CMD\", \"/usr/local/bin/vericto-proxy\", \"healthcheck\"]` (Docker Compose\n`healthcheck.test`, ECS `healthCheck.command`). It connects to the healthz listener\n(`VERICTO_HEALTHZ_PORT`) and exits 0 when that succeeds or the port is not set,\n1 otherwise. A `CMD-SHELL` check, `bash -c`, `curl` or `wget` fails in this image.\n\nTo build the image from this checkout instead:\n\n```\ndocker build -t vericto/proxy:local .\n```\n\nWith Docker Compose:\n\n```\nproxy:\n  image: ${VERICTO_PROXY_IMAGE:-vericto/vericto-proxy:4.15.2}\n  ports:\n    - \"5433:5433\"\n  environment:\n    UPSTREAM_HOST: postgres\n    UPSTREAM_PORT: \"5432\"\n    PROXY_LISTEN_PORT: \"5433\"\n    # For MySQL: VERICTO_WIRE_PROTOCOL: \"mysql\" + the matching ports.\n    # Optional — uncomment for telemetry + rule sync (all three together):\n    # VERICTO_API_URL: \"https://api.vericto.com\"\n    # VERICTO_API_KEY: \"${VERICTO_API_KEY}\"\n    # VERICTO_DATABASE_ID: \"${VERICTO_DATABASE_ID}\"\n```\n\n|  | Connection string | \n|---|---|\n| **Postgres via proxy** | `postgres://user:pass@localhost:5433/mydb` | \n| **MySQL via proxy** | `mysql://user:pass@localhost:3307/mydb` | \n\nSame credentials as a direct connection — only the host/port change.\n\nWire-protocol, TLS, telemetry and deployment fixes belong here; rules and\nparser changes belong in [vericto-engine](https://github.com/vericto/vericto-engine).\nSee [CONTRIBUTING.md](https://github.com/vericto/vericto-proxy/blob/main/CONTRIBUTING.md) and [SECURITY.md](https://github.com/vericto/vericto-proxy/blob/main/SECURITY.md).\n\nCopyright 2026 Vericto S.A.S. Source-available under the\n[Functional Source License, Version 1.1, ALv2 Future License](https://github.com/vericto/vericto-proxy/blob/main/LICENSE)\n(SPDX `FSL-1.1-ALv2`) since 4.14.0: use, modify and redistribute it for any\npurpose other than a Competing Use, and each version becomes Apache-2.0 two\nyears after it is released. Versions 4.13.0 and earlier remain under the\nElastic License 2.0. [LICENSING.md](https://github.com/vericto/vericto-proxy/blob/main/LICENSING.md) answers the common questions,\nincluding how the embedded vericto-engine is licensed; see also\n[NOTICE](https://github.com/vericto/vericto-proxy/blob/main/NOTICE) and [TRADEMARKS.md](https://github.com/vericto/vericto-proxy/blob/main/TRADEMARKS.md).\nFor a commercial license contact [enterprise@vericto.com](mailto:enterprise@vericto.com).\n\nThe Docker image carries `LICENSE`, `NOTICE` and `THIRD_PARTY_LICENSES` (the\nlicenses of every dependency compiled into the binary) in\n`/usr/share/doc/vericto-proxy/`, and the label\n`org.opencontainers.image.licenses=FSL-1.1-ALv2`:\n\n```\n# The image has no shell or `cat` (4.15.2 and later), so copy the file out:\nid=$(docker create ghcr.io/vericto/vericto-proxy:latest)\ndocker cp \"$id\":/usr/share/doc/vericto-proxy/THIRD_PARTY_LICENSES - | tar -xO\ndocker rm \"$id\" >/dev/null\n```\n\n", "url": "https://wpnews.pro/news/show-hn-vericto-check-ai-agents-sql-before-postgres-or-mysql-runs-it", "canonical_source": "https://github.com/vericto/vericto-proxy", "published_at": "2026-10-23 14:16:26+00:00", "updated_at": "2026-10-23 14:42:18.574411+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["Vericto", "vericto-proxy", "vericto-engine", "PostgreSQL", "MySQL", "Docker Hub", "Snowflake", "SQLite"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-vericto-check-ai-agents-sql-before-postgres-or-mysql-runs-it", "markdown": "https://wpnews.pro/news/show-hn-vericto-check-ai-agents-sql-before-postgres-or-mysql-runs-it.md", "text": "https://wpnews.pro/news/show-hn-vericto-check-ai-agents-sql-before-postgres-or-mysql-runs-it.txt", "jsonld": "https://wpnews.pro/news/show-hn-vericto-check-ai-agents-sql-before-postgres-or-mysql-runs-it.jsonld"}}