Reaching production data without handing out the password
A self-hosted portal that puts ad-hoc production SQL behind approval, masking and an immutable trail; it became the QueryProxy product.
What it does today
Runs a developer's SQL against production through an approval step rather than directly; results are masked as they are written to disk and every request lands in an immutable record. Teams where production access sits with one person can run it today.
- Started
- September 2026 — September 2026
- Left Labs
Form
Technologies
This one became a product:
QueryProxyIf a bug shows up in production, that is also where it has to be diagnosed. But handing the production password around is not a solution — and keeping it with one person stops being one the moment that person takes a holiday.
Looking at the data without holding the password
The attempt was this: let a developer read production data without ever seeing the credentials. The connection details sit encrypted inside the application, and the query runs on the portal rather than on the developer’s machine. What stays in the developer’s hands is the SQL they wrote, and nothing else.
The cost is an accepted indirection: the query no longer runs the instant it is written. In return the production password never reaches a human being, and one person’s calendar stops setting the team’s diagnosis speed.
The statement is parsed, not pattern-matched
A gate built on pattern matching would have been bypassed on day one — slip a comment in, split the string, hide inside a subquery. Incoming SQL is parsed instead: what a statement is comes from its resolved structure, not from its text.
In practice an UPDATE or DELETE without a WHERE is refused, an unbounded
SELECT comes back with a default row limit, and anything touching database or
user management is rejected outright. Comments and string literals are
normalised, so writing DROP/**/DATABASE does not open the gate.
A limit expression the gate cannot understand is not clamped but rejected — a bound you cannot read is a bound you cannot enforce. On top of that the executor holds an absolute row ceiling that applies whatever the SQL claims, so even a query that slips through cannot fill the result store.
Approval runs through one service
Approvals arrive through three channels — the web UI, Slack, Teams — and all three land in one service. Because the rule lives in a single place, adding a channel does not duplicate it: a DBA approving their own request is closed everywhere — a system admin can override it, and the override lands in the audit record — and when two channels decide at once a conditional update drops the second. In chat callbacks the actor is resolved from an admin-managed mapping rather than from an address the request declares about itself, so holding a shared signing secret is not enough to approve as someone else.
Masking happens on write, not on read
The last decision is where masking sits. Storing the result first and masking it on display was the easy path; in that design unmasked data reaches disk once and everything after it is a matter of hope.
Instead the result stream is read row by row and each row is masked before it is written to the file. Not one unmasked cell enters the result store, and both the viewer and the download read an already-masked file. The same discipline applies to the rules themselves: a regular expression is stressed against a long probe string when it is saved, and if it cannot be evaluated at runtime the value is masked rather than left in the clear.
The Labs phase closed in three days — that is how clear the need was. QueryProxy is a product with its own domain and releases now; the details are in the portfolio.