Skip to main content

Auditing a database that is not yours

Most SQLens runs are a team pointing it at their own database. This page is about the other case: an agency, a consultant or a contractor running it against a client's system.

The short version: get the engagement in writing first, and keep it. Everything below is why, and what "in writing" needs to contain.

This is a documentation page, not a feature

SQLens asks you nothing before it connects. There is no consent prompt, no authorization flag, and nothing in the tool that checks whether you were allowed to run it — the credentials you hand it are the only thing it consults.

That is deliberate, and it is worth being plain about: the tool cannot tell an authorized audit from an unauthorized one, and building something that pretended to would be worse than saying so here.

Why a documented engagement, and not a verbal one

Reading someone else's database is an access to a production system that produces a report about its weaknesses. Three things follow, and none of them depends on anybody acting in bad faith:

  • The client's own controls will see it. A connection from an unfamiliar host, a role that suddenly reads the catalog, a burst of information_schema queries — that is what an incident looks like from the inside. A document is what turns a three-hour escalation into a one-minute lookup.
  • The report outlives the conversation. A SQLens report names roles, privileges, missing constraints and, in the security suite, weak authentication settings. It is the kind of document that gets forwarded. The engagement is what says who may hold it.
  • "The developer said it was fine" is not the client. The person who gave you a connection string is often not the person who can authorize an assessment of the system it points at. That is not a technicality; it is the usual case in an agency relationship.

What an engagement should contain

Not a legal form — a note both sides can point at later. At minimum:

  • Who is asking. The client organization, and a named person who can answer for it.
  • What is in scope. Which databases, on which hosts or which managed instances. A scope that says "the client's systems" is a scope nobody can check you against.
  • What is out of scope, where that is not obvious. A shared cluster is the case that bites: one connection string can reach neighbors the engagement never named.
  • When. A start and an end. An open-ended authorization is one nobody can revoke without a conversation.
  • Who to reach, and how fast. Someone technical, reachable during the window. This is the line that matters when a monitoring alert fires at 23:00.
  • What happens to the report. Where it is stored, who receives it, and when it is deleted.

This is not legal advice, and it is not a template that fits every jurisdiction or contract. It is the set of questions that, unanswered, turn a routine audit into an argument.

What SQLens is not

  • It is not a penetration-testing tool. It reads catalogs and state views with the privileges it was given. It does not probe, does not enumerate hosts, does not try credentials, and does not attempt to reach anything it was not pointed at.
  • It does not work around access control. A privilege the role does not hold is reported as undetermined with the privilege named. There is no path in the code that escalates, retries as another identity, or asks for more.
  • It does not read your client's data. Every query reads a catalog or a state view. No user table is selected from, no EXPLAIN is run, and no row contents reach a finding.

The first point is the one worth repeating to a client who is nervous about the word "audit": what SQLens does is closer to reading the blueprints than to trying the doors.

Which commands touch a foreign system, and which never do

CommandTouches a client database?What it reads
sqlens:lintNoMigration files in your checkout. Pretend mode emits SQL without a connection.
sqlens:formatNo.sql files on disk. It needs no connection at all.
sqlens:baselineNoMigration files, and the baseline file in your repository.
The analyse extensionNoPHP source, through PHPStan. It is a PHPStan extension rather than a command.
sqlens:agent-rulesNoThe active rule set, written into files in your repository.
sqlens:auditYes, read-onlyThe catalog: tables, columns, indexes, constraints.
sqlens:securityYes, read-onlyThe catalog plus server settings, roles and privileges.
sqlens:predeployYes, read-onlyServer version and variables, live lock and replication state.
sqlens:postdeployYes, read-onlyThe catalog, after a migration ran.
sqlens:driftYes, read-onlyThe catalog, compared against what your migrations describe.
sqlens:doctorYes, read-onlyWhether each configured connection answers, and its version.
sqlens:mcpYes, read-onlyNothing of its own — it serves the commands above to an agent, with their limits.

Two rows deserve a sentence of their own.

sqlens:lint in shadow mode is the exception to its own row. Shadow capture builds a scratch database to replay migrations into, so it needs more than read access — and that database is created on whichever server you point it at. Never point it at a client's production instance; the shadow mode page covers the setup and the guard that refuses production.

sqlens:security --refresh-advisories is the one command that reaches the network, and only because you typed it. Everything else runs from data that shipped with the package.

If you are the client

You can bound this from your side, and it is the more reliable half:

  • Give the auditor a role of their own, with the read-only grants on the least-privilege audit role and nothing more. What SQLens cannot see, it reports as undetermined rather than working around.
  • Point it at a replica where one exists. Every read SQLens makes is answerable from one.
  • Revoke the role when the window ends. That is the part a document cannot do for you.