lock family docs GitHub

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)
  1. The agent submits SQL with locksql plan (or the locksql_plan MCP tool). The console classifies it, parses and analyses a read down to the source column of every output, runs EXPLAIN, weighs the plan and returns a one-shot plan id with a verdict (OK, WARN or REFUSE).

  2. 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 a k-anonymity warning for each count under k_anonymity) and answers the agent pending with 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…
    
  3. 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).