TraLand.com
In development · not yet released

SafeCoQuery

Let the agent query the real database. Decide what it can see.

A macOS database client for developers who hand a real database to an AI coding agent. The client half is table stakes. The product is the policy layer between the agent and the database — masking columns, hiding schemas, filtering rows, blocking statements, holding writes for human approval, and recording every disclosure.

MySQL first · macOS · built for Claude Code, Codex and other local agents

“How do we stop the agent reading customer PII?”

That is the question SafeCoQuery exists to answer. The usual answer is a read-only database user pointed at a scrubbed replica — which is free, takes about twenty minutes, and is genuinely good at the part it covers. Here is the part it doesn’t.

What a GRANT already does well

  • Stops writes outright, at the database
  • Keeps whole tables and columns out of reach
  • Costs nothing and needs no new software
  • Enforced by the database itself, not a client

What it cannot do

  • Approve a write. A read-only user says no to everything; there is no path for “yes, that one, now, with a human looking at it.”
  • Tell you what was disclosed. Database logs see one credential and a connection — not which session asked, what policy decided, or how many rows came back.
  • Revoke instantly. Changing grants means a DBA and a round trip.
How it works

Every query goes through policy you control

The agent connects to SafeCoQuery, not to your database. Each statement is parsed and checked against the policy before anything reaches the server, and what comes back is shaped on the way out — columns masked, rows filtered, results capped.

Only a human can change any of that. There is no agent-facing path to alter a mode, a mask, a filter or a limit; an agent cannot widen its own access by asking.

01

Agent sends a statement

Over its own connection, separate from yours, with its own identity.

02

Policy decides

The statement is parsed, not pattern-matched. Blocked statements never reach the database.

03

A write waits for you

Writes need explicit human approval and run inside a transaction that rolls back if they exceed the row cap you set.

04

The disclosure is recorded

Including how many rows the agent actually received — in a tamper-evident log.

The policy

A file you own, that fails closed

Policy is a file, not a dialog you clicked through once. It reviews in a pull request, and the defaults are deliberately unhelpful to an agent: anything nobody has classified is invisible until a human classifies it.

version: 1
connection: production-replica

# Anything nobody has classified yet. Both default
# to block: a column added next month is invisible
# until a human looks at it.
unknown_column_policy: block
unknown_table_policy: block
disclose_policy: full

# Patterns give coverage; explicit columns give
# correctness. Without patterns, drift fails open.
patterns:
  - match: [password, passwd, secret, token, apikey,
            privatekey, cvv, cvc, pin, hash, salt]
    policy: BLOCK
    locked: true   # an ALLOW cannot relax this

  - match: [card, card_number, pan, iban, sort_code,
            account_number]
    policy: MASK(card)
    locked: true

  - match: [email, phone, mobile, telephone, msisdn]
    policy: MASK(tokenize)

  - match: [ssn, nino, dob, birthdate, passport, licence]
    policy: MASK(redact)
    locked: true

# Explicit rules. These win unless a pattern is
# locked.
columns:
  customers.internal_notes: BLOCK
  customers.signup_source: ALLOW

# The agent's query is rewritten to include these;
# it cannot see or remove them.
rows:
  orders: "tenant_id = 42"

schemas:
  mysql: HIDDEN
  information_schema: HIDDEN

# Per-table sign-off. An unreviewed table is invisible.
reviewed:
  - customers
  - orders

Locked patterns beat a column list

Patterns cover the columns nobody has got to yet; explicit rules get the specific ones right. locked: true settles the conflict — an administrator pinning password to BLOCK cannot be undone later by someone adding a per-column ALLOW.

An unreviewed table is invisible, not merely unfiltered

Per-table sign-off is the only granularity a human realistically finishes. The cost is that the default patterns have to be genuinely complete, because inside a reviewed table every unmatched column is allowed — which is exactly why payment identifiers ship in the defaults, and locked.

Schema drift is the thing that catches people

A column added after the policy was written is the realistic failure. Both unknown policies default to block, so new columns and new tables start invisible and become visible only when someone decides they should be.

The controls

Mechanisms, not adjectives

Each of these is a thing the software does, and each is set by a human.

Write approval

A write is held until you approve it, then runs inside a transaction that rolls back if it affects more rows than you allowed. A read-only account cannot offer this; it can only refuse.

Session-level audit

The log records the principal and session, the statement as asked and as actually run, the policy applied, and how many rows came back. A database log sees one credential; this records which session received what.

Instant revocation

A kill switch you can hit yourself, that survives a restart — no DBA, no grant change, no waiting for a round trip.

Column masking

Named columns never appear in results. Useful, and the easiest thing to demonstrate — but read the inference caveat below before you lean on it.

Schema and row scoping

Hide whole schemas from discovery, and attach filters so the agent only ever sees the rows you scoped it to.

Statement blocking

Statements are parsed with a real SQL parser and refused by kind, so a blocked statement never reaches the server at all.

Audit

A log the agent cannot read, reorder or quietly trim

Every audit method — read, verify, export — is refused on the agent’s connection, and refused in a way that is indistinguishable from a method that does not exist. A record of what something did is not much use if its subject can review it for gaps.

Records are chained with SHA-256, and the head of the chain is anchored in the macOS Keychain. You can export the whole log yourself — you pick the destination through a normal save panel, and the file carries the original bytes of each record rather than a re-serialisation, so someone else can re-walk the chain and confirm nothing was altered, reordered or removed. The app re-walks the file before finalising it. If verification fails the export still happens, and says so: a tampered log is exactly what you would want to hand an investigator.

Scope

What SafeCoQuery is not

This is a security product, so the boundary matters more than the pitch. These limits are deliberate and we would rather you read them here than discover them later.

It is not a database-side security boundary.
It does not restrict what the credential you give it can do. Use a least-privilege account.
Masking hides values, not inferences.
Masking prevents a value from appearing in results. It does not prevent inference about that value from aggregates over related columns.
Masking happens in transmission, not at rest.
It is a transform applied to results on their way through SafeCoQuery. It does not change what is stored, and anything holding a database credential and a route to the server can read the raw table with SafeCoQuery not involved at all.
It governs database access, not your machine.
It controls the agent’s database access. It is not a defence against arbitrary local code — an agent with shell access on your machine is outside this boundary, as is anything else running as your user.
The audit chain is tamper-evident, not externally anchored.
The hash chain detects accidental corruption and naive in-place edits, deletions and reordering. With a Keychain-anchored chain head it also detects wholesale replacement and tail truncation. It is not an external anchor.

Tell me when it ships

SafeCoQuery is working software but is not released yet — there is no download and no pricing. Leave an email and we will tell you when that changes.

One email when it ships. Nothing else, and we don’t pass it on.