One Query From DROP TABLE

Read-only SQL access for AI agents

The Stakes

Give an agent real data — without giving it DROP TABLE

AI agents are only useful when they can touch real data. But handing an LLM a raw SQL connection is one hallucination away from destroying it. The Amplifier database tool gives agents read-only, bounded access instead.

So how big is the thing standing between the agent and the table?

The whole safeguard is 450 lines — and every test passes

The safety package in src/database_tools/safety/ is five small Python files. In the current checkout, the full suite runs green.

Small and tested — but why not just one good filter?

450
lines in the safety package (5 files)
67
tests, all passing (~2.6s)
Why One Filter Isn't Enough

One missed keyword and the table is gone — silently

A single keyword the check overlooks, or a write smuggled past it, means silent data loss. One layer of checking is never enough to trust with production data — the answer has to be layered defense.

The tool stacks independent layers. Here's the first.

The validator blocks dangerous keywords before the query runs

It strips comments, then uses case-insensitive word-boundary matching. DDL is always rejected; writes are rejected in the default read-only mode.

The forbidden query never reaches the database. Next: bounding what a permitted read can pull.

Every result set is clamped so a query can't drain the table

The LimitEnforcer rewrites a too-large LIMIT or injects one when it's absent. Default 100 rows, hard maximum 1000.

That bounds size. The next layer bounds time.

100
default row limit
1000
hard maximum rows

A runaway query is aborted once it passes 30 seconds

A TimeoutHandler wired into SQLite's progress handler checks every 10,000 VM instructions and aborts the query once elapsed time exceeds the limit.

Three independent layers so far. Now the backstop beneath them all.

30s
default query timeout
10k
VM instructions per check

Even if a validator misses, the engine itself refuses to write

On connect in the default read-only mode, the adapter runs PRAGMA query_only = ON. The write is made physically impossible at SQLite — the earlier layers become a net over an already-locked door.

Layered checks over an engine-level lock. That's the pattern.

# adapter, on connect (default mode) if self.mode == "read_only": self.connection.execute( "PRAGMA query_only = ON" ) # writes now fail at the engine UPDATE ... → blocked

Defense in depth, with the write path off by default

Phase 1 ships three read-only tools. Writes (db_execute) and other engines are explicitly deferred to Phase 2. Layered, independent safeguards with the write path closed by default is how you give agents real data safely.

Read-only, bounded, backstopped — agents get real data, the table stays intact.

Sources

Research Methodology

Subject: amplifier-module-tool-database (Phase 1), version 0.1.0

Feature status: Active — Phase 1 (SQLite + read-only SELECT); PostgreSQL/MySQL and write ops deferred to Phase 2

Repo history: single initial commit 13856db, Wed Jan 21 14:54:40 2026 -0800, author Sam Schillace (sole commit author)

Research performed (source commands):

Gaps & caveats: Docs frame "6 layers" (PHASE1_COMPLETE.md / SAFETY.md), but only 5 are enforced in code — the 6th (connection-string / log sanitization) is documented-but-unbuilt, and bundle.md itself lists only 5. Parameter binding is supported but not enforced. Repo docs report 59/67 (88%) as of the commit date; the checked-out code now passes 67/67 because the DDL/DML fix is present. Self-reported effort figures (~40 hours, ~3,500 lines) are not git-derivable; measured src+tests = 3356 lines.

Primary contributor: Sam Schillace — sole commit author (single initial commit).

More Amplifier Stories