Skip to content

UnknownFilterTokenError 漏诊:含非 word 字符的类占位符串({TODAY()}、{current-user-id}、{30 days ago})绕过诊断,原样下发按字面串比较(17.0.0-rc.2) #5586

Description

@yinlianghui

Part of objectstack-ai/hotcrm#786

现象

UnknownFilterTokenError 的覆盖面止于占位符文法 /^\$?\{([a-zA-Z0-9_]+)\}$/。任何含非 word 字符的类占位符串——{TODAY()}{current-user-id}{30 days ago}{user.id}——在 classifyFilterToken 第一行就 return null,不被识别为令牌,于是绕过诊断、原样下发给 driver 按字面串比较——正好落回该诊断本要消灭的失败模式(静默错误结果,而非显式报错)。

证据(pinned 17.0.0-rc.2,InMemoryDriver + 真 ObjectQL.find;crm_task 四行夹具,due_date 分别在 30 天前 / 1 天前 / 今天 / 7 天后)

classifyFilterToken:
  {today}            => {"kind":"date-macro","token":"today"}
  {TODAY}            => {"kind":"unknown","token":"TODAY"}     → 读路径 THROWS UnknownFilterTokenError ✅
  {TODAY()}          => null                                    → 不识别,原样下发 ❌

读路径:
  due_date <  '{today}'    =>  2 行(恰好两行逾期,解析正确)
  due_date <  '{TODAY()}'  =>  4 行(含 7 天后才到期的——字面串比较 + 字典序倒置,'2026-…' < '{')

即:拼错成不带括号的 {TODAY} 会得到显式报错(诊断达成目的),拼错成带括号/连字符/空格/点号的形态反而静默返回错误行集——后一类恰恰是作者从其它系统的宏语法(TODAY()、kebab-case、自然语言)迁移时最容易写出的形态。

参照:应用侧守卫比平台诊断更严,次序反了

hotcrm 的作者期守卫用更宽的 /\{([^{}]+)\}/ 扫描 view filter 值,能逮住上述全部形态——所以 hotcrm 今天在 CI 层碰不到这个洞。但这层保护只覆盖一个应用仓库的作者期;运行时(API 调用方、其它应用)仍然裸奔。平台诊断的存在理由就是消灭「类占位符串静默按字面量比较」,现在的文法让最常见的错拼形态恰好漏网。

两种可能修法(供上游取舍,应用侧未做任何 workaround)

  1. 宽进严出:诊断的识别文法放宽到 /\{[^{}]+\}/(任何花括号包裹的串都视为「作者意图写占位符」),词表校验不通过即抛 UnknownFilterTokenError——把「不认识」从静默字面量比较变成显式报错;若担心误伤真的想比较字面 {…} 字符串的场景,提供显式逃逸(如 \{)。
  2. 保守版:文法不动,但对进入比较运算的字符串值增加一条启发式诊断(值以 { 开头、以 } 结尾而未被识别为令牌 → warning 级日志/validate 告警),不改变行为,先让漏网形态可见。

发现过程:objectstack-ai/hotcrm#782 / PR objectstack-ai/hotcrm#785 的注释订正实测(一次性探针,证据全文在该 PR body)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions