
请求清洗、服务端授权、Schema检索和SQL复核是四个不能互相替代的边界。摘要我沿 Microi吾码AI 当前 NL2SQL 链路从 V8 请求绑定追到角色策略、FormEngine 权限、Schema 检索与 SQL 校验。结论是提示词不能授予数据权限边界来自服务端白名单取交集、空集合失败关闭以及生成后逐表复核。本文绑定当前源码、行号与 55/55 聚焦测试不把本地测试冒充生产验收。✦① 提示词不是权限系统让大模型“只查询有权限的表”听起来合理却有一个根本问题模型看到的文字本身不能证明调用者是谁也不能证明这个人此刻拥有哪些菜单、表和数据范围。如果客户端还能直接传 AllowedTables攻击者只要把数组改成敏感表名提示词就成了装饰。Microi吾码AI 当前链路把问题拆成四层先清掉客户端自报的可信字段再由服务端计算候选表Schema 检索只在候选表里找相关项模型生成 SQL 后还要逐个检查来源表、语句类型和结果上限。任何一层失败都不继续向数据库放行。客户端能表达问题但不能替服务端声明租户、用户、白名单和行数上限。先给结论安全的自然语言查询不是“模型答应不越权”而是模型从未拿到越权 Schema生成结果也无法绕过服务端复核。✦② 第一层把客户端的“可信字段”清零在 V8 入口当前代码从认证上下文写入 OsClient、当前用户 Id 和名称同时把调用方传来的 AllowedTables 置空把 ServerAuthorizationApplied 改为 false并把 ServerMaxRows 清零。这一步的价值不是格式化参数而是明确谁有资格产生可信状态。param.OsClient _osClient; param.CurrentUserId _currentUser[Id]?.ToString(); param.AllowedTables null; param.ServerAuthorizationApplied false; param.ServerMaxRows 0;信任方向问题文本从客户端进入租户、用户、授权标记、表集合和行数上限只能由服务端向下游注入。✦③ 第二层候选表要经过三次取交集服务端先拿当前租户的普通业务表集合受保护的控制面表不会因为用户等级高就自动暴露。普通角色还要把 AI 角色策略给出的表、调用者希望缩小的范围以及 FormEngine 的真实读取权限依次求交集。客户端请求只能缩小不能扩大。先限定当前租户的普通业务表排除受保护的控制面表。再与 AI 角色策略、调用方主动缩小的范围求交集。最后叠加 FormEngine 的真实读取权限得到服务端白名单。租户普通业务表 ∩ AI角色策略表 ∩ 调用者请求的缩小范围可选 ∩ FormEngine真实读取权限 服务端AllowedTables当前实现还为权限结果使用带授权版本号的十分钟缓存。缓存 Key 同时包含租户、授权版本、用户和候选表签名权限版本变化后不会继续命中旧快照。缓存读取失败时转为实时校验而不是默认放行。空集合语义交集结果为零时直接拒绝查询。零不是“让模型自己猜”也不是退回全租户 Schema。✦④ 有行级范围的表为什么宁可不查表级可读不等于可以看整张表。某个菜单可能带 SqlWhere只允许看本人、部门或关联记录通用 NL2SQL 如果只拿到表名却没有稳定复现这些条件就会把局部权限误当成全表权限。当前批量授权只接受两类表角色有明确的表级 Read 权限或者至少有一个已授权且没有 SqlWhere 的菜单。带行级范围的菜单不会进入通用查询白名单。需要本人、部门或复杂关联范围时正确做法是写参数化、经过管理员审核并有审计的业务 ApiEngine。失败关闭当通用 SQL 无法证明能重现数据范围时拒绝比“尽力加一个条件”更诚实也更安全。✦⑤ 第三层检索相关Schema也必须在授权之后检索命中回答“哪些表相关”授权回答“哪些表可见”两者不能调换。关键词检索始终可用只有显式启用向量数据库时才增加 Qdrant/Ollama 通道。但无论是关键词结果还是向量结果当前代码都会带入同一份服务端 AllowedTables。因此向量召回再聪明也只能在被授权的表集合里排序。返回给界面的 SchemaCandidateCount 也是授权过滤后的候选数量。它可以解释“本次命中了几张相关表”却不会把未授权表名或字段当成诊断信息泄露出去。概念分离相关性检索不是授权向量相似度不是权限候选数量也只能统计授权后的集合。✦⑥ 第四层模型写完SQL还要逐表验一次执行层首先要求服务端授权标记为真且白名单非空然后归一化表名和最大行数。SQL 词法检查会读取每个 FROM 与 JOIN 来源只要子查询、显式连接或任一来源出现未授权表整条语句就失败。没有引用任何授权表的 SELECT 1 也不会放行。生成SQL → 只允许受控只读SELECT → FROM / JOIN / 子查询逐表匹配 → 禁止多语句、危险函数和逗号连接 → 按数据库方言追加最大行数 1 → 只返回授权上限内的结果多取一行用于判断是否截断最后只返回授权上限内的数据。这里也要讲清边界当前是保守词法门禁不应宣传成完整 SQL AST模型生成的动态值也不会自动改写为数据库参数。高风险条件仍应回到显式参数化接口。✦⑦ 这次真实跑了55项聚焦测试当前本地 no-build 聚焦运行55 通过、0 失败、0 跳过。我在当前 Microi.Server 运行 NL2SqlSecurityPolicyTests55 项、55 项通过、0 项失败、0 项跳过VSTest 报告 99 ms命令墙钟约 2.225 秒。用例覆盖双 JSON 序列化伪造可信字段、空白名单、越权 JOIN 与子查询、向量结果精确过滤、多数据库行数上限、危险函数、多语句和只读语句。这组测试证明的是当前本地授权上下文与 SQL 安全策略行为它没有拿生产账号查询真实业务数据也没有证明目标租户的角色策略已经配置完整。生产验收仍需登录真实角色核对候选数量、查询结果和审计记录。验证边界源码、单元测试和生产权限配置是三件事本文只确认前两件。✦⑧ 结尾把权限做成可计算的集合安全的 AI 数据助手不需要假装模型永不犯错。更可靠的设计是把租户表、角色策略、真实表权限、无行级范围条件、Schema 候选和 SQL 来源都变成可计算、可记录、可失败关闭的集合。因此评审一个自然语言查询能力时可以连续问四句谁清除了客户端伪造字段白名单由哪些权威数据求交集未授权 Schema 会不会进入检索生成 SQL 的每个来源表是否再次验证四句都能落到代码和测试提示词才只是体验层而不是被迫承担权限系统。可信租户、用户和白名单是否只由服务端写入零张授权表时是否立即失败关闭Schema 检索与 SQL 来源表是否都使用同一份授权集合源码证据、聚焦测试与生产角色验收是否分别陈述本文验证范围基于 2026-08-23 当前本地源码 SHA、行号与 55/55 聚焦测试未执行生产查询、未修改平台数据、未声称线上角色策略已验收。