SQLite.

How to protect a SQLite database with Vericto. SQLite has no server, so there is no proxy: evaluation happens in your code, with the Runtime API, the CLI or MCP (Python and Node SDKs coming soon).

SDKs: coming soon. The SQLite dialect and rules VERICTO-098 to VERICTO-101 are available in the Runtime API, the Validation API, the CLI and MCP. The Python (vericto) and Node (@vericto/sdk) SDKs are not published on PyPI or npm yet: the Python and Node sections describe how they will work.

Overview

SQLite is one more Vericto dialect (vericto-engine 4.1.0): the same deterministic engine parses each statement with the sqlite dialect and evaluates it against your workspace rules, sensitive columns and agent allowlists. Register the database with the SQLite dialect in Dashboard → Databases and protect it from any of these paths:

  • Runtime API: your code sends each query to POST /api/v1/proxy/query with "dialect": "sqlite" and runs it only if Vericto lets it through.
  • CLI in CI/CD: vericto check --dialect sqlite on your migrations.
  • MCP: your coding agent calls check_sql with dialect: "sqlite" before it runs SQL on a local database.
  • Python or Node SDK (coming soon): a wrapper around sqlite3 or better-sqlite3 that will evaluate each statement before the driver runs it.

Why there is no TCP proxy

SQLite is not a server: it is a library inside your application process, and each query is a C function call on a file. There is no socket and no network protocol a proxy could sit on. That is why a SQLite database registers in HTTP mode only, and the dashboard offers no proxy connection details for it.

The enforcement point is your code. The Runtime API call (or, once published, the SDK wrapper) is what applies the decision. SQL run through another connection to the same file, bypassing that check, is not evaluated. As a second layer, the SQLite authorizer (sqlite3_set_authorizer, Connection.set_authorizer in Python) can deny ATTACH and PRAGMA when each statement is prepared.

Python: vericto.sqlite (coming soon)

Coming soon. The Python SDK is not published on PyPI yet; this section describes how it will work. It will use an API key with the proxy:evaluate scope and the registered database. vericto.sqlite.connect() will return a real sqlite3.Connection (pandas and other libraries keep working) whose execute, executemany and executescript are evaluated before they run; executescript will evaluate every statement of the script before running it. Until then, use the Runtime API.

export VERICTO_API_KEY=vtro_...          # scope proxy:evaluate
export VERICTO_DATABASE_ID=<database-id> # the SQLite database in the dashboard
import vericto.sqlite
from vericto import VerictoBlocked

conn = vericto.sqlite.connect("app.db")
conn.execute("DELETE FROM sessions WHERE expires_at < ?", (now,))  # allowed: it runs

try:
    conn.execute("DELETE FROM sessions")  # no WHERE: blocked, nothing runs
except VerictoBlocked as exc:
    print("blocked:", exc)

Node.js: @vericto/sdk/better-sqlite3 (coming soon)

Coming soon. The Node SDK is not published on npm yet; this section describes how it will work. guardDatabase() will wrap a better-sqlite3 database. The entry points that carry SQL (prepare, exec and the run/all/get shortcuts) will become async; a prepared statement will be evaluated once and its run/all/get stay synchronous. Until then, use the Runtime API.

import Database from 'better-sqlite3';
import { guardDatabase } from '@vericto/sdk/better-sqlite3';

const db = await guardDatabase(new Database('app.db'));

const expire = await db.prepare('DELETE FROM sessions WHERE expires_at < ?'); // checked once
expire.run(Date.now());
await db.exec(migrationSql); // every statement of the script is checked first

Both SDKs will fail closed by default (VERICTO_FAIL_MODE=closed): if Vericto does not answer, the statement does not run. A response with rewritten_query (a masked column) will run instead of the original.

Runtime API

From any other language or driver (Go database/sql, Rust rusqlite, Cloudflare D1 from a Worker), send each query before running it. 200: run it (or run rewritten_query when present); 403: do not run it. The full contract is in the Integration Guide.

curl -X POST https://api.vericto.com/api/v1/proxy/query \
  -H "Authorization: Bearer $VERICTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "ATTACH DATABASE '\''/tmp/x.db'\'' AS x", "database_id": "<database-id>", "dialect": "sqlite"}'

# 403 → {"decision": "BLOCKED", "rule_code": "VERICTO-098", "severity": "high", ...}

CLI in CI/CD

Check SQLite migrations (Drizzle, Prisma, Django, Rails, Alembic, Flyway) before the merge. The SQL is parsed, never executed. Store default_dialect = "sqlite" in .vericto.toml so you do not repeat the flag.

VERICTO_API_KEY=vtro_... vericto check migrations/*.sql --dialect sqlite

SQLite migrations rebuild tables with PRAGMA foreign_keys = OFF. VERICTO-099 reports it, and it is a SchemaMigration rule: the CI channel's schema migration cap lowers it to FLAGGED, like a DROP TABLE, while at runtime it blocks.

AI agents (MCP)

A coding agent working on a local SQLite database calls check_sql with dialect: "sqlite" before it runs the SQL with its own tool. whoami lists the workspace databases with their dialect. Setup and quota: AI agents (MCP).

SQLite rules

Four rules that only fire on SQLite (details in the rules reference):

  • VERICTO-098 ATTACH DATABASE (HIGH): ATTACH opens, or creates, another database file on the connection: arbitrary file access and reads across databases. DETACH is not reported.
  • VERICTO-099 Safety PRAGMA (HIGH): writable_schema, trusted_schema = ON, foreign_keys = OFF, ignore_check_constraints = ON, journal_mode = OFF, synchronous = OFF, query_only = OFF, schema_version, among others. Reads (PRAGMA table_info(t)) and safe settings (journal_mode = WAL, busy_timeout) are not reported.
  • VERICTO-100 Extension loading or file functions (CRITICAL): load_extension() (native code), fts3_tokenizer() and the sqlite3 shell's readfile(), writefile(), edit(), fsdir() and zipfile(), anywhere in the statement.
  • VERICTO-101 VACUUM INTO (HIGH): a full copy of the database written to a path. A plain VACUUM is not reported.

Of the standard rules, 15 apply as they are, 5 have a SQLite variant (for example, WHERE '1abc' is true in SQLite) and 11 do not apply because SQLite has no such construct (TRUNCATE, GRANT, MERGE…). The list is in the rules reference.

Sensitive columns and masks

Sensitive columns work on SQLite. A mask tag rewrites the projection with core SQLite functions: full, last4 and email. With the Runtime API, rewritten_query runs (the SDKs will do the same once published). Some masks block the query instead of rewriting it:

  • hash blocks on SQLite. Core SQLite has no SHA-256 (it needs an extension), so the mask cannot be applied and the query is blocked. Use full, last4 or email, or the block policy.
  • A double-quoted name blocks the mask. SQLite reads "x" as a string when no column x exists (DQS mode), so rewriting that query could change a literal. With masks, write names unquoted or in brackets, and turn DQS off on your connection (SQLITE_DBCONFIG_DQS_DML=0). The block and flag policies count "x" as a column, the cautious reading.
  • ORDER BY, GROUP BY or HAVING on a masked column, or an unaliased expression over it, block the query: SQLite would resolve those names to the masked value.

Agent access

In agent allowlists, SQLite names (schema, table and column) compare case-insensitively, as SQLite compares them, and "x", `x` and [x] are accepted. An entry without a schema is the table in main; temp and attached databases are other schemas. sqlite_master, sqlite_schema and the pragma_* functions need an entry that names them. Under an allowlist, ATTACH, DETACH, VACUUM, REINDEX, ANALYZE and PRAGMA are denied, except structure reads of an allowed table (PRAGMA table_info(t)) and the connection settings drivers and ORMs send (foreign_keys = ON, busy_timeout, journal_mode = WAL…).

Parse errors

A SQLite statement the parser does not read follows the workspace parse-error policy, as on every dialect: flagged by default, and blocked under parse_error: block, a column tagged block or mask, or an allowlist in enforce. The SQLite statements the parser does not read but the engine knows are still recognised: the dangerous ones (VACUUM INTO, PRAGMA … = OFF) as findings of their rule, and the harmless ones (PRAGMA table_info(t), DETACH, REINDEX) with no finding.

Each database can replace the workspace policy with its own (unparseable SQL per database): Inherit, Flag or Block. Recommended: Block when AI agents access the database.