任务架构

划清 Android FTS 字面搜索与查询语法边界

分别验证 SQL 参数绑定、FTS3/FTS4/FTS5 查询语法及用户字面搜索意图,不把语法变化误称为 SQL 注入。

审核状态: 内容已校验;中文稿供用户审核 · 2026-10-08 · Fact IDs: ANDROID-FTS-QUERY-GRAMMAR-2026-10, ANDROID-APPSEARCH-HISTORY-2026-10, ANDROID-PRIVACY-01

直接回答

把用户字符串绑定到 `MATCH ?`,可以保护 SQL 语句结构不被字符串直接拼接破坏,但不会把右侧值变成普通文本。SQLite 会把绑定后的字符串交给全文检索引擎,将其解析为 FTS 查询表达式。因此,即使没有 SQL 注入,含引号、前缀符、字段选择器、`NEAR` 或布尔运算符的值仍可能改变搜索含义或产生语法错误。设计和事故报告要分开描述两个边界:SQL 参数绑定处理 SQL 结构;字面搜索策略处理 FTS 小语言和用户意图。 Room 搜索注解必须结合包名和 artifact 版本阅读。Room 2 的 `androidx.room` 命名空间提供 `@Fts3` 和 `@Fts4`。文档中的 FTS5 注解是 `androidx.room3.Fts5`,来自 `androidx.room3:room3-common`,Room 3.0.0 才加入;Room 文档链接到该 API,不代表 Room 2 依赖也能使用它。即使 Room 3 FTS5 能编译,也不能证明运行时 driver 能创建该表:Android 文档注明 `BundledSQLiteDriver` 支持 FTS5,而 `AndroidSQLiteDriver` 路径取决于设备 SQLite 能力。要核对实际 Room/compiler artifact、生成 schema、driver,并在真实数据库执行 feature test。语法也有差异:FTS3/4 有 standard/enhanced 集合运算模式,FTS4 增加词首 `^`,FTS5 邻近语法是 `NEAR(...)`,不同于 FTS3/4 的 `NEAR/N`。 为默认产品体验选一个窄而明确的模式。字面模式应把用户输入当作待搜索内容,而不是运算符、字段名、前缀或任意查询语法。正确编码依赖所选 FTS 模块和 tokenizer。使用维护中的库,或使用针对目标运行时验证过的 parser-aware builder;不要承诺一个 regex 或 SQL 引号替换能安全编码所有 FTS 语法。如果应用无法可靠建立这个边界,就限制支持的字符范围,或退回较简单的精确 ID 查询,并将高级 FTS 语法设为独立显式模式,不要把普通文本默默解释成命令。 对 FTS 表达式及其他用户值仍使用绑定参数。绝不要把搜索文本拼接到 SQL 标识符、列名、表名或 DAO 语句里;标识符和 schema 形状应来自固定应用代码。即使表达式经过验证的 FTS 编码器处理,参数绑定仍然必要,因为两层解决的问题不同。限制输入长度、词项数量和结果数量,对连续输入做防抖,并安全处理空串、只有标点、格式错误和过复杂表达式。值可安全绑定到 SQL 层,但仍可能形成昂贵或超出预期的 FTS 搜索,因此还要测试资源行为。 为字面短语模式保留带版本的夹具集合,并断言实际 DAO 返回的准确文档 ID。至少包括普通词、空格、撇号、内部双引号、括号、星号、冒号、连字符、大写运算符词、组合附加符、中文、emoji 和纯标点输入。既要测试看似语法的文本,也要测试有意启用的语法。断言字面内容不会变成字段过滤或布尔扩展,并分别记录 tokenizer 如何处理重音、大小写和词边界。分词后没有词项时可以返回空结果,不要把这种输入回退成范围更大的查询。 如果产品确实需要前缀、短语、邻近或布尔组合等高级语法,应把它做成单独模式,明确公开语法和控制项,并为 parser 设定有界合同。SQLite FTS3/4 的 standard 与 enhanced query syntax 不同,FTS5 也有自己的运算符和 tokenizer 选项。字段选择器只能指向固定索引字段,不能采用用户输入的列名。测试错误表达式、目标语法支持的嵌套分组、仅否定项、罕见运算符和最大查询长度。不要把高级搜索混同 SQL 注入:真正要控制的是返回结果是否偏离意图、语法处理是否过重,以及是否绕过应用搜索范围。 本文讨论查询边界,不评估搜索质量或声称已有产品集成。FTS 命中只是检索候选,不证明模型生成答案受来源记录支持。本地数据库和 FTS 表也不能单独解决备份、删除、授权或隐私问题。展示敏感历史前,要按当前访问策略重新读取权威数据行;索引状态应纳入应用自身的数据生命周期。Cove 源码和已有 AppSearch 文章不能证明 Cove 当前使用 Room FTS。

搜索保存笔记时,标点被当作查询语法

用户输入 `plan (draft)` 查找本地摘要。Room DAO 已把字符串绑定到 `MATCH` 占位符,但选定 FTS 引擎仍会将值解析为查询表达式。搜索返回错误或意外记录,团队最初把问题称为 SQL 注入。

决策

区分 SQL 绑定、FTS 语法解析、查询模式策略和授权;默认字面模式使用针对实际模块验证的语法感知编码器。

预期结果

普通搜索文本按约定当作字面内容处理;高级运算符只有显式启用后才生效,不支持的语法明确失败且不会扩大结果集。

需要收集的证据

只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:

  • ANDROID-FTS-QUERY-GRAMMAR-2026-10:MATCH 绑定参数仍会由所选 FTS 模块按其查询语法解析。
  • ANDROID-APPSEARCH-HISTORY-2026-10:本地搜索索引是检索投影,命中不能替代当前数据行授权。
  • ANDROID-PRIVACY-01:数据库本地存储本身不能证明备份、删除与隐私边界。

任务质量需要有版本记录的语料和验收规则;流畅的单条样例不等于准确度结果。

实施流程

  1. 识别 Room API 与运行时 driver. 记录 Room 主版本、包命名空间/compiler artifact、生成的 FTS schema、SQLite driver 和设备矩阵。Room 2 的 androidx.room 是 FTS3/4;androidx.room3.Fts5 要求 Room 3.0.0+。必须在实际 driver/数据库验证 FTS5,不能只看编译成功。
  2. 分开定义字面和高级模式. 明确普通输入意味着一个词、一个短语还是受限文本。前缀、字段选择器、布尔运算符和邻近语法放入单独且有意开启的高级模式。
  3. 使用两层安全查询构造. 将完整 MATCH 表达式作为 SQL 参数绑定,并使用按模块测试过的编码器或 parser,防止字面内容变成 FTS 运算符。表名与列名必须来自固定应用代码。
  4. 对语法和 tokenizer 做夹具测试. 在实际 DAO 与 SQLite 引擎上测试引号、标点、运算符词、前缀、Unicode、组合附加符、空输入、错误语法和超长文本。断言返回行 ID,不只断言调用没有抛异常。
  5. 限制结果并保留来源授权. 限制查询长度和结果数;出错时不回退到更广泛查询。展示命中前重新读取当前获准的数据行;搜索匹配是候选,不是经验证的模型答案。

让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。

验收标准

  • 识别 Room 主版本、API 命名空间、compiler artifact、生成 FTS schema、SQLite driver 与 tokenizer;用实际 driver/数据库证明 FTS5 运行时支持。
  • 所有用户值仍使用 SQL 参数绑定,同时由独立 parser-aware 策略控制 FTS 表达式语言。
  • 字面夹具证明标点和类似运算符的文本不会静默扩大或重定向预期搜索。
  • 高级语法、结果限制、错误和当前来源授权都有明确结果,不夸大 SQL 注入或搜索排序结论。

决策对照表

维度选择或证据解读方式
绑定参数的 `MATCH ?`绑定用户值而不是拼接进 SQL。保护 SQL 语句结构,但绑定值仍由 FTS 语法解析。
字面短语模式使用适用于所选 FTS 模块的已测试编码器。仅能在经过验证的语法、tokenizer、SQLite 版本和字符范围内保持用户意图。
高级 FTS 模式将文档化运算符作为用户明确选择的能力。需要 parser/资源限制和固定字段白名单;它与 SQL 注入不是一回事。
LIKE 或精确 ID 回退字面 FTS 编码未验证时改用更简单查询合同。匹配语义和性能会变化,应如实说明,不能假称等同全文检索。
Room 版本与 driver按 Room 主版本选注解包/artifact,再验证 driver。Room 2 androidx.room 是 FTS3/4;Room 3.0.0+ 才有 androidx.room3.Fts5。编译不证明运行时支持。

需要避免的失败模式

  • 不要把 MATCH ? 的 SQL 参数绑定误说成 FTS parser 会忽略语法符号。
  • 不要默认所有 Room 项目都使用 FTS5 或相同 SQLite tokenizer。
  • 不要用未经目标模块验证的通用 regex 替换引号或运算符。
  • 不要把 FTS 语义变化等同 SQL 注入,也不要让 advanced query 决定应用授权。

还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。

最小复现记录

层级记录内容
设备厂商、型号、芯片、内存档位、操作系统版本
软件应用版本和 Git 提交号
模型系列、变体、版本、格式、文件长度、摘要、量化方式
运行时名称、版本、请求后端及实际后端证据
工作负载样例版本、输入摘要、提示词摘要、输出策略
结果完成、失败、取消、回退及保护隐私的诊断信息

发布检查表

  • ☐ 主要搜索问题已回答,且没有未经支持的数字。
  • ☐ 所有模型文件和运行时版本均已固定。
  • ☐ 代表性任务和失败处理策略均有说明。
  • ☐ 生命周期、取消、清理和回退均已测试。
  • ☐ 用户内容与网络边界均有说明。
  • ☐ 结论只适用于被记录的配置。
  • ☐ 新增测试结果时链接到原始方法或证据。

来源与相关证据

中文部署与排错专题见。奇连 AI 端侧专题。

常见问题

参数化 `MATCH ?` 能防止 SQL 注入吗?

参数绑定可保护 SQL 语句结构,用户文本不会被拼成 SQL。但 SQLite 仍会把绑定值解析为 FTS 查询语法,因此还要单独测试和限制 FTS 表达式。

Room FTS5 注解能证明 Android 运行时支持吗?

不能。androidx.room3.Fts5 要求 Room 3.0.0;Room 2 使用 androidx.room FTS3/4。Android 文档说明 BundledSQLiteDriver 支持 FTS5;AndroidSQLiteDriver 取决于设备 SQLite。要测试实际 driver 和生成 schema。

能用一个通用正则转义 FTS 用户文本吗?

不要假定通用正则有效。引号和运算符随 FTS 模块与 tokenizer 而异。使用针对目标引擎测试的 parser-aware 编码器,或限制允许的字面输入范围。

高级 FTS 运算符本身就是 SQL 注入漏洞吗?

当值已绑定时,运算符本身不是 SQL 注入。但如果不控制语法和字段策略,它仍可能改变搜索含义、导致错误、增加工作量或绕过应用范围。