这个坑值得记一笔。问答助手带了一个 run_query 只读工具,模型把“帮我把这条标记为已读”理解成 SQL,生成了一句 UPDATE,数据库账号恰好有写权限,直接改了三行。
刚开始盯着「数据库账号给了读写权限」,折腾了半个多小时,排掉了。
排查方向其实有限,先把可能性排一排。只读靠的是提示词约束,不是数据库权限;SQL 校验只检查了开头是不是 SELECT;没有限制语句里出现分号和多条语句。
先看历史调用里有没有非查询语句
python -c "import json;d=json.load(open('sql_log.jsonl'));print(len(d))"
再往下把数据库账号降成只读
python -c "print('grant=SELECT only')"
接着在校验层用解析器判断语句类型
python -c "import sqlglot;print(sqlglot.parse_one('UPDATE t SET a=1').key)"
末了用一条 UPDATE 做回归确认被拦
python tool_try.py --tool run_query --sql 'UPDATE ticket SET st=1' --expect reject
别只看报错消失,验证要多做一步。UPDATE 与 DELETE 在校验层直接拒绝,只读账号执行写语句报权限错误,正常 SELECT 的延迟没有变化。
只读必须由数据库权限兜底,提示词和字符串前缀检查都挡不住会写 SQL 的模型;账号只给 SELECT 是最省心的做法。报错文案容易误导,真正的原因往往在相邻那一层的日志里。
