Locks Β· locksql
How locksql works, in detail
The README has the short version. The full walkthrough of a session is in usage.md.
Two processes, one key holder #
Two processes in two containers. Only the console holds the key.
sequenceDiagram
autonumber
participant A as π€ AI agent<br/>(locksql mcp / CLI)
participant C as π locksql console<br/>(your terminal)
actor H as π€ You
participant D as ποΈ Database
A->>C: plan "SELECT β¦ LIMIT 20"
C->>C: classify Β· parse Β· resolve PII columns
C->>D: EXPLAIN
D-->>C: plan & row estimate
C-->>A: plan id + verdict (OK / WARN / REFUSE)
A->>C: run PLAN_ID
C->>D: k-anonymity counts
C-->>A: pending, ticket T
C->>H: ticket T in lockhub (warns of a count under k)
H-->>C: lox approve: signed decision
A->>C: run T
C->>D: the statement
D-->>C: rows
C-->>A: masked, capped rows (untrusted data)
-
The agent submits SQL with
locksql plan(or thelocksql_planMCP tool). The console classifies it, parses and analyses a read down to the source column of every output, runsEXPLAIN, weighs the plan and returns a one-shot plan id with a verdict (OK,WARNorREFUSE). -
The agent calls
locksql run PLAN_ID. When the statement filters, groups or aggregates PII, the console first runs its k-anonymity counts. It prints the approval screen in its terminal, sends the request to lockhub as a ticket (every literal masked, with ak-anonymitywarning for each count underk_anonymity) and answers the agentpendingwith the ticket:ββ DEV Β· 127.0.0.1 / app Β· user alice Β· tier read βββββ β requested by uid 1000 (alice) Β· pid 48211 (claude) β agent: list the Belgian customers to check the email campaign β β SELECT id, email FROM customers WHERE country = 'BE' LIMIT 20 β β class READ Β· EXPLAIN: customers range ~410 Β· est. 410 rows examined Β· verdict OK β reads: app.customers β returns at most 20 rows β PII columns touched: customers.email (select) β masked outputs: email β redact β PII: masked (4 column rules; detectors: email, phone, iban, card) β°β pending: ticket 01Jβ¦ -
You decide the ticket in lockhub (
lox approve). The agent runs the ticket (locksql run TICKET); the console checks the hub's signed decision, plans the statement again, runs it, masks PII, caps the output and hands the rows back to the agent, framed as untrusted data.
Process layout
Console container (your terminal) Agent's container
βββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β locksql console --profile dev β socket β locksql mcp (stdio MCP) ββββ AI agent
β credentials (ask | Vault) β volume β locksql plan|run|status|pii ββββ AI via shell
β policy Β· classifier Β· EXPLAIN βββββββββββββΊβ no secrets, no DB connection β
β approval prompt Β· masking Β· auditβ JSON-RPC ββββββββββββββββββββββββββββββββ
βββββββββββββββββββ¬ββββββββββββββββββ
β native protocol
βΌ
MariaDB Β· MySQL Β· PostgreSQL Β· SQLite
Every client command, locksql mcp and locksql doctor also take --instance NAME, to
reach one lock among several of this type that lea mounts (without it:
$LOCKSQL_INSTANCE, else the one lock in $LEA_LOCKSQL_INSTANCES, else, with
several, the lock whose directory holds the profile's console socket: one
locksql mcp serves them all). locksql console
and locksql hub-key take an --instance too, but that one is the name lox run
gives the lock.
Full walkthrough: docs/usage.md.
Engines #
| Engine | Versions | Read-only session (tier read) |
Server-side timeout | Plan source |
|---|---|---|---|---|
mariadb |
10.1+ (tested 10.11, 11.4) | SET SESSION TRANSACTION READ ONLY + START TRANSACTION READ ONLY |
max_statement_time + KILL QUERY |
EXPLAIN FORMAT=JSON |
mysql |
8.0+ (tested 8.0, 8.4) | same as MariaDB | max_execution_time (SELECT only; writes rely on KILL QUERY) + KILL QUERY |
EXPLAIN FORMAT=JSON |
postgres |
13+ (tested 13, 17) | SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY + BEGIN READ ONLY β¦ ROLLBACK |
statement_timeout + cancel request |
EXPLAIN (FORMAT JSON, VERBOSE) |
sqlite |
3 (pure Go, in-process) | mode=ro + query_only |
interrupt on deadline | EXPLAIN QUERY PLAN + sqlite_stat1 (run ANALYZE once, or every full scan is a WARN of unknown size) |
All engines are pure Go: the binary is built with CGO_ENABLED=0. locksql
runs in Linux containers, on amd64 and arm64, wherever Docker runs
(docs/docker.md).