SELECTだけ許しても、メールアドレスは読める。LLMにSQLを作らせるなら、「更新しない」に加えて、読ませる列をDB接続側で絞りたい。
手元では、名前だけ返すSQLのWHEREにメールアドレスを入れた。自分はここも止めたい。
SQLiteで列アクセスを拒否する
Pythonのset_authorizer()に判定関数を渡す。main.users.nameの読み取りとcountを許可し、ほかは拒否する。LLMには接続せず、SQL実行部分を検証する。
Python 3.14.6/SQLite 3.53.4で実行済み。column_guard.pyに保存し、python3 column_guard.pyで動く。追加パッケージは不要。DBはメモリ内に作る。
import sqlite3
db = sqlite3.connect(":memory:", cached_statements=0)
db.executescript("""
CREATE TABLE users(name, email);
INSERT INTO users VALUES('Aki', 'aki@example.test');
""")
print("制限前:", db.execute("SELECT email FROM users").fetchall())
def authorize(action, a, b, schema, source):
if action == sqlite3.SQLITE_SELECT:
return sqlite3.SQLITE_OK
if action == sqlite3.SQLITE_READ:
if (schema, a, b) == ("main", "users", "name"):
return sqlite3.SQLITE_OK
if action == sqlite3.SQLITE_FUNCTION and b == "count":
return sqlite3.SQLITE_OK
return sqlite3.SQLITE_DENY
db.set_authorizer(authorize)
queries = [
"SELECT name FROM users",
"SELECT email FROM users",
"SELECT name FROM users WHERE email='aki@example.test'",
"SELECT count(*) FROM users",
"SELECT count(name) FROM users",
]
for i, sql in enumerate(queries, 1):
try:
print(i, db.execute(sql).fetchall())
except sqlite3.DatabaseError as e:
print(i, type(e).__name__ + ":", str(e))
db.close()
制限前: [('aki@example.test',)]
1 [('Aki',)]
2 DatabaseError: access to users.email is prohibited
3 DatabaseError: access to users.email is prohibited
4 DatabaseError: not authorized
5 [(1,)]
3番も止まった。
SQLiteはSQLのコンパイル時に認可関数を呼ぶ。SQLITE_READではDB名・テーブル名・列名を見られるので、WHERE内の参照も判定できる。SQLITE_DENYで文全体を拒否する仕様は公式資料で確認した。
countの許可だけでは足りなかった
4番は少し意外だった。関数のcountは許している。それでも止まる。
公式資料によると、count(*)のように列値を取り出さない参照では、SQLITE_READに空の列名が渡る。手元の引数は(None, 'users', '')だった。許可条件には合わない。検証ではキャッシュを無効にし、毎回コンパイルさせている。
5番は許可したnameを読むので通る。ただし、count(name)はNULLを数えない。全行数が欲しいなら、公開してよいNOT NULL列を用意するなど、集計の意味まで決める必要がある。名前が必須かどうか、ここでスキーマ設計に戻ってくる。
SQL実行ツールに持ち帰るなら
許可列の取得と機密列を使う条件式を、セットで回帰テストに残したい。この例は列単位の制限なので、行ごとの閲覧権限や実行時間には別の対策が要る。認可関数は接続ごとに登録し、未信頼SQLを渡す前から固定して使う。
SQL生成が上手くなるほど、正しく動くSQLの中に読ませたくない列が混じる場面も増えそうだ。返る表だけ見て安心せず、条件式で触る列までテストで押さえておく。