04 Visp DB Access open source · v0.1.0 · self-hosted · PostgreSQL & MySQL

Production, without the keys.

Visp DB Access is a self-hosted gateway for querying production PostgreSQL and MySQL. Every statement is parsed, checked against your access and the cluster’s policy, rewritten with hard limits, run in a guarded transaction, masked and audited. Engineers never see the database credentials.

docker pull ghcr.io/djkeshawa/visp-db-access:0.1.0
Engineer · read accesscommerce · production

SELECT id, email, total FROM orders WHERE created_at > now() - interval '1 day'

analyze · run
GatewayAllowed · risk low
  1. recordSQL, identity, verdict and client IP saved before the database is touchedfirst
  2. limit… LIMIT 1001 · policy caps reads at 1,000 rows, +1 to detect truncationrewritten
  3. runread-only transaction · 15 s statement timeout · always rolled back37 ms
  4. routeread replica, healthy within the last two minutesreplica
  5. maskemail → ••••••masked
  6. UPDATEThe same engineer can’t write here: production policy blocks writes until you allow them, and then a second person approves each one.
An illustrative read on a production PostgreSQL cluster with a replica and email masked. Default production policy.
  1. 1NetworkClient IP inside the org and cluster allowlists403
  2. 2AccessYour highest unexpired grant: read, write or admin403
  3. 3AnalyzeThe whole statement, including CTEs and subqueriesallow · approve · deny
  4. 4RecordSQL, identity and verdict saved before any database I/Ofails closed
  5. 5LimitRow cap, timeouts, cost gate, concurrency slot422 · 429
  6. 6ExecuteGuarded transaction; reads always roll backDatabase
  7. 7Mask, auditMasked columns hidden, final status in historyrecorded
One statement, start to finish. If the history write in step 4 fails, the query is refused, so nothing reaches the database unrecorded.

Verdicts

Allow, approve, or refuse.

Choose a statement to see what the gateway decides and why. Writes hidden inside a read still count as writes.

SELECT * FROM orders WHERE email LIKE '%@example.com'
Verdict
allow read access is enough
Runs as
The same query with LIMIT 1001 appended, in a read-only transaction that is rolled back
Suggests
select_star and leading_wildcard: name the columns you need, and a pattern that starts with % can’t use an ordinary index. Suggestions never change the verdict.
WITH gone AS (DELETE FROM sessions WHERE expires_at < now() RETURNING id)
SELECT count(*) FROM gone
Verdict
deny writes_disabled on production
Why
It looks like a SELECT, but the CTE deletes rows. The gateway analyzes the whole statement, so this is a write, and production’s default policy blocks writes.
Recorded
As blocked in query history and the audit log
UPDATE users SET name = 'Maya Patel' WHERE id = 42
Verdict
needs approval risk high · staging · write access
Next
Request approval with a reason. A cluster admin who isn’t you approves or rejects it within 24 hours, and either of you runs it once.
Guarded
Both grants are checked again when it runs, and the write rolls back if it changes more than 1,000 rows
DELETE FROM orders
Verdict
deny write_without_where
Why
An UPDATE or DELETE without a WHERE clause is refused in every environment, whatever your access. So is an always-true one such as WHERE 1 = 1.
SELECT pg_sleep(600)
Verdict
deny dangerous_function
Why
Sleeps, file access, advisory locks, dblink and similar functions are refused, along with sensitive catalogs such as pg_authid and mysql.user.
ALTER TABLE orders ADD COLUMN note text
Verdict
deny ddl_disabled by default, in every environment
If allowed
With allow_ddl on the cluster’s policy, an admin can submit it, and it always goes through approval at risk critical.
Verdicts under the default policies. Production blocks writes and DDL; staging and development allow writes, with approval required by default.
Read-only
The default. Writes need a write grant, a policy that allows them and, by default, a second person’s approval. DDL always needs approval.
Approvals
Four eyes: a reviewer can never approve their own request. Approvals expire after 24 hours and are claimed once, so they can’t be replayed.
Limits
Statement and lock timeouts, row, byte and affected-row caps, an optional EXPLAIN cost gate, per-cluster concurrency limits, replica routing and cancellation on the server.
Credentials
Encrypted with AES-256-GCM under your master key and write-only in the API. Engineers query through the gateway and never see them.
Masking
Column patterns such as users.ssn or *.password* are masked in results, exports and RETURNING.
Discovery
Finds RDS and Aurora databases across AWS accounts and regions with read-only permissions. Import is always an explicit admin action, and drift is flagged.
One binary
A Rust server with the React console embedded. Stateless nodes, all state in a PostgreSQL metadata store, a distroless Docker image and a Helm chart.

The gateway enforces

  • Who may run what, on which cluster
  • Hard limits on every statement
  • Approvals for writes and DDL
  • A record of every query and change

Still yours to set up

  • Least-privileged database roles for the gateway
  • Restricted views for truly sensitive data
  • Outbound firewall rules to your databases
  • Load-balancer affinity for cancellation
Defense in depth, not a replacement for database grants. Masking matches returned column names, so an alias or computed expression can get around it; the known limits are listed in full.

Get started

From a clone to a guarded query.

Docker Compose with sample PostgreSQL and MySQL databases. For production, use the image on GHCR or the Helm chart.

Deployment guide
  1. 1

    Choose the keys.

    The server refuses to start without a valid 32-byte master key. Keep it in a secret manager: stored credentials can’t be decrypted with a different one.

    git clone https://github.com/djkeshawa/visp-db-access && cd visp-db-access
    export VDA_MASTER_KEY="$(openssl rand -base64 32)"
    export VDA_BOOTSTRAP_ADMIN_PASSWORD='choose-a-long-random-password'
  2. 2

    Start it with demo data.

    The demo profile adds PostgreSQL and MySQL databases with a sample shop.

    docker compose --profile demo up --build
  3. 3

    Register a cluster and query it.

    Open localhost:8080, sign in as admin@example.com, and add demo-postgres (database shop, user demo, password demo-local-only) in the development environment.

Keep going