When you connect a BI tool or query assistant to your database, there are two distinct layers of access control — and most teams only think about one of them.
Understanding both layers, and how they interact, helps you make better decisions about what to configure and what to trust.
The two layers
Layer 1: Database user permissions
This is the access control enforced by the database itself. When you create a database user with GRANT SELECT ON ..., you’re configuring what that user is allowed to do at the database level. The database engine enforces this unconditionally — no application can override it.
If a user only has SELECT permission, any INSERT, UPDATE, DELETE, or DROP statement issued under that user’s credentials will be rejected by the database engine with a permission error. This happens regardless of what application is making the connection.
This layer is your foundational control.
Layer 2: Application-level enforcement
This is what the tool itself does before it sends a query to the database. A tool that has “safe mode” or “read-only mode” is running its own checks on the generated SQL before executing it.
For example: the tool receives your natural language prompt, generates a SQL query, validates that it’s a SELECT statement (rejecting anything else), and only then executes it.
This layer can catch things that database-level permissions don’t, like:
- Blocking
SELECTqueries that are structurally dangerous (e.g., missing aWHEREclause on a large table) - Blocking schema inspection queries you don’t want the user to run
- Rate limiting queries per user or per connection
But application-level enforcement has a fundamental limitation: it depends on the application working correctly.
Why both layers matter
The two layers are complementary, not redundant.
| Database permissions | Application enforcement | |
|---|---|---|
| Enforced by | Database engine | Application code |
| Can be bypassed by | Having credentials + direct DB access | Bugs, misconfigurations, or intentional override |
| Scope | What the user is allowed to do | What the application allows the user to do |
| Survives app bugs | Yes | N/A (it is the app) |
| Survives direct DB connection | Yes | No |
The key insight: application-level enforcement only applies when the query goes through the application. If credentials are leaked and an attacker connects to the database directly, application-level read-only mode is irrelevant. The database user’s permissions are all that matters.
Conversely, database permissions don’t protect against all types of unsafe queries — they only reject queries that the user isn’t permitted to run. A read-only user can still run a SELECT that reads every row in your largest table and causes performance issues.
Defense in depth
Using both layers together means:
- Even if the application has a bug and generates a write query, the database rejects it
- Even if credentials are leaked and someone connects directly, they can only read (not write) data
- Even if someone bypasses application enforcement, they’re bounded by database permissions
Neither layer alone provides this complete coverage.
Comparing configurations
| Configuration | DB breach + credentials | App bug generates write | Direct DB connection |
|---|---|---|---|
| Root user, no app enforcement | Full access | Executes | Full access |
| Root user + app read-only | Full access | Blocked by app | Full access |
| Read-only user, no app enforcement | Read-only | Rejected by DB | Read-only |
| Read-only user + app read-only | Read-only | Blocked by app and rejected by DB | Read-only |
The bottom row is the configuration to aim for. It provides protection at both layers, in both the application path and the direct connection path.
What to look for in a tool
When evaluating a query tool:
Application-level read-only enforcement:
- Does the tool explicitly validate that generated queries are read-only before executing them?
- Is this enforced at the application layer, not just at the UI layer?
- Does it block DDL statements (DROP, ALTER, CREATE) in addition to DML (INSERT, UPDATE, DELETE)?
Database connection:
- Does the tool support or recommend connecting with a least-privilege database user?
- Does the setup documentation show how to create a read-only user?
The right answer to both is yes. Application enforcement and database-level scoping should be treated as complementary, not alternative.
The practical checklist
Before connecting any database tool:
- Create a read-only database user — see guides for PostgreSQL and MySQL
- Scope the user to the tables you need — see limiting access to specific tables
- Confirm the tool enforces read-only at the application layer — check documentation or ask the vendor
- Verify the setup — connect, run a
SELECT, then attempt anINSERTorUPDATEand confirm both behave as expected