ThearchyHelios
|
26ff8e1e06
|
只读数据源真的只读了:WITH … DELETE 不再从首关键字下面钻过去,连接本身也写不进去
守卫只看首关键字,而 with / pragma 都在只读白名单里。在只读的 SQLite 源上
实测:`WITH k AS (SELECT 1) DELETE FROM t` 被放行,5 行删得只剩 2 行;
`PRAGMA user_version = 7` 也写进去了。调用方只看到一个 ResourceClosedError——
pysqlite 只在 INSERT/UPDATE/DELETE 开头的语句前隐式开事务,WITH 开头的直接
自动提交,连接关闭时的回滚兜不住。Excel/CSV 上传生成的正是这种 SQLite 源。
两层一起收:
守卫:以查询开头的语句(SELECT / WITH / EXPLAIN)要把正文扫一遍,先抹掉注释
和引号里的内容,再找写关键字(DELETE、UPDATE、INTO、FOR UPDATE、EXPLAIN
ANALYZE 里的语句……),关键字后紧跟 ( 的是同名函数不算(MySQL 的 INSERT()、
TRUNCATE())。PRAGMA 改成只读白名单,`= 值` 和 `(值)` 两种赋值写法都拦。
另收一小撮有副作用的函数,其中 set_config 必须拦:PG 真库实测,它能把连接层
的只读关掉,同一连接的下一个事务就能写。函数名加引号、dblink 另开连接这两种
绕法也补上了。is_write 与只读判定共用一份判定,免得审批那道门另有一套口径。
连接层:只读源连接时就只读(engine_args)。SQLite 以 mode=ro 打开(file: URI,
路径带空格、中文、# 都测过);PostgreSQL 设会话默认事务只读;MySQL / MariaDB
设会话事务只读。Oracle 没有会话级开关,只剩守卫——README 写明请配只读账号。
真库验证(临时实例,已清理):PostgreSQL 18 与 MySQL 8.0 上,DELETE、WITH … DELETE、
PG 的数据修改 CTE、nextval、EXPLAIN ANALYZE 绕过守卫直连执行,都被数据库以
只读事务拒绝;可写源不受影响。
测试:守卫新增 33 例(藏在查询里的写、会写的 PRAGMA、同名函数与引号标识符不误杀),
去掉修复重跑是 17 例失败;test_readonly_source.py 6 例(当初那条绕过、绕过守卫
直连写、中文路径、可写源不受波及、各库的会话参数)。297 → 334 passed。
顺带查到但没在这里修的:可写源上的写操作既不审批也不生效——is_dangerous_call
全仓库没人调用,而 run_query 从不 commit,写完就被回滚。两件事必须一起修
(先接审批再让写入生效,反过来就是一个无审批写库的窗口),放在审批统一那一步。
|
2026-09-25 14:44:56 +08:00 |
|
ThearchyHelios
|
a127f7049c
|
真实 Oracle 生产库跑通,修掉三处只有连真库才暴露的问题
在一个企业 BI 库(SAP 物料/采购/凭证 + MES 生产 + IOT 设备)上端到端验证。
连接 3 秒握手,thin 模式,确实不需要 Instant Client。三处问题只有连真库才看得见:
## options.schema 被当成驱动参数拼进了连接串
我自己引入的:build_url 把 options 里所有剩余的 key 都拼成 query 参数,
而 schema 是探查配置不是连接参数,于是 ?service_name=BI&schema=ANALYTICS 直接
被驱动拒掉。options 里混着两类东西,现在显式区分。
## 只读账号名下一张表都没有
这是企业环境的常态而非例外:TAIKE 名下只有 2 个 SYNONYM,数据在 ANALYTICS
(51 个视图) 和 STAGING(9 张表)。默认探查当前用户 schema 得到 0 张表,而原来
只会安静地返回一个空壳——用户完全不知道下一步该干什么。
现在:options.schema 可指定探查目标;探不到任何对象时自动列出可访问的非系统
schema(排掉 MDSYS/CTXSYS/XDB 那一屏 Oracle 内部对象),摘要里直接告诉
Copilot"该账号名下没有对象,数据可能在这些 schema"。
## 表名不带 schema 前缀直接 ORA-00942
只读账号访问的是别人 schema 下的对象,SELECT * FROM V_TRIP_FACT 必然失败。
schema_cache 里为每个对象记 qualified 全名,summary / table_names /
db_query 的工具描述统一用全名——模型照着写就不会漏前缀。describe_table 则
反过来放宽:全名、裸表名、大写形态都认(Oracle 惯用大写,SQLAlchemy 返回小写)。
## 实测
探查 53 个对象 7.8 秒,摘要约 2000 token(含一个 273 字段的宽视图),在预算内。
Oracle 的中文表/列注释正确读出("不合格数据明细表"、"工厂代码,复合主键之一")。
agent 自主查询 8 轮:列对象 → 挑 3 个 MES 视图看结构 → 逐个 COUNT + MAX(load_time)
→ 补查业务时间字段,最后报出 v_demo_table 有 365,633 行、最新数据
2026-09-17 06:53。全程自己带对了 schema 前缀。
安全边界在生产库上复验:
- UPDATE / DELETE / DROP 全被拒,数据未变
- 19.87 亿行的 TG_ECC_MSEG 裸查 SELECT *:0.7 秒返回 200 行即断开,
流式读取没有把表拉回来
- 203 列宽表的字节上限在第 37 行触发
凭据只进加密库(Fernet 密文,已核验库中无明文),不在仓库、提交历史或任何输出里。
|
2026-09-17 11:44:22 +08:00 |
|
ThearchyHelios
|
39e7a5b014
|
数据源接入:MySQL / Oracle / PostgreSQL / SQLite
阶段①。产品原先能取数的只有 http_request 和沙箱里自己写代码——面对"分析上季度
华东区销量下滑"这类需求,用户得自己知道数据在哪张表、字段叫什么,再手写 SQL 塞进
代码节点。最难的部分留给了用户。
## 连接层(app/data/)
方言差异全收敛在 engine.py 一处:Oracle 要 service_name/sid、MySQL 要 charset、
各家默认端口不同。三个驱动都是 asyncio 原生且免装客户端——oracledb 走 thin 模式,
不需要 Instant Client。密码复用 app/core/crypto 的 Fernet,和 Provider 同一套;
build_url 默认把密码替换成星号,日志和错误信息里用这个版本。
introspect.py 产出两种粒度:summary()(表名+代表字段,几百 token,给 Copilot 的
system prompt 用)和 describe_table()(单表完整字段,按需取)。分两种是因为一个
中等业务库全量塞进 prompt 轻松几万 token,既贵又会把真正的需求淹掉。
## 守卫(app/data/guard.py)
这是唯一不能出错的地方:SQL 由模型生成,模型会写出什么事前无法穷举,防线不能
建立在"它应该不会这么干"上。三层:
- 只读判定用**白名单**而非黑名单——黑名单永远漏,MERGE/CALL/GRANT/各家私货写不完
- 多语句在到达驱动前就断掉,不赌驱动默认只跑第一条(MySQL 开了
CLIENT_MULTI_STATEMENTS 就会全跑)
- 行数/字节/超时三重上限,一条 SELECT * 不该把内存和模型上下文一起打爆
刻意不引 sqlparse:完整解析 SQL 方言是无底洞,而这里的判断很窄——"首关键字是
什么"和"有没有第二条语句"。窄问题用窄解法。难点是不能误杀:`SELECT ';'` 只是
一条语句,简单 split(";") 会把它切两半然后拒掉,那比漏杀更让人无从下手。
## 工具(app/tools/datasource.py)
每个数据源动态生成一对 db_query__<源> / db_schema__<源>,而不是一个通用工具带
datasource 参数:工具名自带语义,模型不会把查销售库的 SQL 发给财务库;description
里直接塞表清单,省掉"先问有哪些表"那一轮。接进 registry.build_tools 作为第四来源
(前三个是内置/MCP/自定义)。
查询结果落 put_json(kind="query_snapshot"),把 SQL 原文 + 结果集 + 数据源一起存,
出具体系的数字回指能下钻到"这个数是哪条 SQL 查出来的"。
## 实测
真实 SQLite 业务库(600 行订单,埋了华东 6 月断崖的信号)+ 真实模型:
agent 8 轮自主查询——列表 → 查三张表结构 → 探数据时间范围 → 查地区维度 →
按月汇总 → 按地区+月份下钻,准确定位到华东 6 月从 1187 件跌到 354 件。
只读守卫在 agent 路径上同样生效(UPDATE/DROP/多语句全被拒且数据未变)。
测试 55 项:守卫 30 项(含"字符串里的分号不能误杀")、连接与探查 15 项、
URL 构造 6 项、Copilot 流式 4 项。
|
2026-09-17 10:39:23 +08:00 |
|