Skip to content

[Decision] security(objectql): may a hook's handler name bind to a function another package registered (the engine-wide fallback HookSchema.handler declares), or does name resolution stay inside the hook's own package (#21585 option B) #21604

Description

@objectstack-fleet

Ruled: 5974477722 · letter B · 2026-10-03T23:13Z

Filing gate: ② a decision only the maintainer can make. It is a security boundary, and the resolution rule is declared in the spec, so changing it is a contract change. Filed by the triage seat (objectstack-wide, seat post #6015, session_01AavokzJ5DndAwitDXvKy4U). It carries option B of the pm:retriage on #21585 (5970562606). #21585 itself is answered A in this round, which closes the install-local door only. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes, doors, positions and functions only.

Reader who acts: the maintainer, or the director seat. If the letter is B, domain:engine carries it, and the spec doc moves in the same PR.

维护者速读

Measured

  • Cross-package binding is real ([finding] os package install accepts a package whose hook uses only the deprecated function-name handler (no body), answers "installed", and the hook never fires: install-local drops it with a server-side warn only #21585 dev report 5970542332, on main 6c5697dffb, through the public door): a package's handler-only hook ran a function that another app registered. This held hot and after a restart. A unit reading on one engine agrees: the resolved function's packageId is the other app's.
  • Mechanism, read now on main b610eabf72:
    • packages/objectql/src/hook-binder.ts, resolveHandler (about :321–:322) tries the bundle's own functions, then engine.resolveFunction(name).
    • The engine's function map is keyed by bare name. The stored owner is not consulted on lookup.
  • Declared: packages/spec/src/data/hook.zod.ts (about :318), HookSchema.handler's doc, says the name may resolve to "anything engine.registerFunction(name, fn) added".
  • Pull for cross-package resolution, measured now: zero known writers.
    • objectstack examples/**: no hook names a string handler. The only string handler is a job's, which resolves within its own bundle.
    • hotcrm src/**: no hook with a string handler.
    • Inside packages/**, registerFunction is called only by the engine, the hook binder and the formula stdlib.
  • Who can reach it: a holder of the metadata-management capability, either by installing a package or by authoring a hook through the metadata door. The open question is whether a package's content, a third-party catalog package above all, should be able to run code it did not ship.

Governing text

一句话问题

一个应用包的钩子,能不能靠「同名」去执行另一个应用包的代码?

选项 × 真实代价

选项 做什么 客户可感知的后果
A 维持 什么都不改,只在文档里写明「按名字跨包共享」是有意的 无变化。装一个第三方包,它可以借同名触发你另一个应用的代码,平台不提示
B 包内解析 名字只在钩子所属的包内解析,跨包必须显式写出包。改 spec 声明,发一个收窄版本 实测今天没有人依赖跨包按名绑定,所以不影响任何人。以后误写的人会在安装或注册时看到报错,指出名字在本包里找不到

业务含义直译

  • A 等于大楼里任何一家租户喊一声「保洁」,来的可能是别家公司请的保洁,而且这件事没人登记。
  • B 等于每家只能叫自己合同里的人;要借别家的人,得写明借谁家的。

四轴(业务立场)

os-decision-facets

  • ① 项目长远合理性:B 让函数归属在解析时生效,收缩一个隐式跨包通道;A 把它固化成契约。
  • ② 实际业务拉动:今天零个跨包按名绑定的写法(examples、hotcrm 实测)。零拉动默认 remove,偏 B。
  • ③ 防 AI 犯错:B 在注册时响亮拒绝;A 撞名时静默执行他包代码。偏 B。
  • ④ 创业阶段不扩散:B 收窄声明,A 维护一个跨包通道。偏 B。

推荐:B。 两年后的样子:每个包的代码按包解析,跨包调用都是显式、可审计的引用。参照 Salesforce:托管包的 Apex 类带命名空间前缀,跨包调用要写全名。
回退:A。 只改文档,写明跨包共享是有意的;#21585 的 A 已经堵住本地安装这个入口。
自检: 只看①选 B;②③④ 是否翻转:否。三轴同向。
置信缺口: 我看不到私有客户的多应用组合。一个 defineStack 里多个应用共用一组函数,按名字跨包绑定,这种写法在示例里没有,但在客户那里可能存在。这正是 B 的认领要先普查的。常设规则「新门默认否」偏向 A,所以维护者不回字时按 A 处理。

裁后执行

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:enginepriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions