Skip to content

crypto.hash 能力声明了、构建期还会自动推断,但沙箱从没实现 —— 调用直接抛(declared ≠ enforced) #4391

Description

@os-zhuang

现象

crypto.hash 是一个声明了、推断了、类型也写了,但沙箱从没实现的能力。作者声明它、CLI 为它通过构建、然后调用在 VM 里直接抛。

defineHook({
  name: 'fingerprint_lead',
  object: 'crm_lead',
  events: ['beforeInsert'],
  body: {
    language: 'js',
    source: "ctx.input.fingerprint = await ctx.crypto.hash('sha256', ctx.input.email);",
    capabilities: ['crypto.hash'],   // 构建接受
  },
});

os build 通过,能力被授予,记录写入时 hook 抛错。

声明面(四层)

层 位置 状态
capability token packages/spec/src/data/hook-body.zod.ts:30 — 'crypto.hash' 在 HookBodyCapability 枚举里 ✅ 声明
文档 同文件 :19 — - `crypto.hash` — `ctx.crypto.hash(algo, data)` ✅ 声明
构建期推断 packages/cli/src/utils/extract-hook-body.ts:52 — { rx: /ctx\.crypto\.hash\b/, cap: 'crypto.hash' },写了 ctx.crypto.hash 的 body 会自动拿到这个能力 ✅ 声明
运行时类型 packages/runtime/src/sandbox/script-runner.ts:117 — hash?: (algo, data) => Promise<string> ✅ 声明
沙箱实现 packages/runtime/src/sandbox/quickjs-runner.ts:607-618 — cryptoObj 上只装了 randomUUID ❌ 没有

grep -n "crypto.hash" packages/runtime/src/sandbox/quickjs-runner.ts 无结果。

为什么值得单独记一笔

这是 Prime Directive #10 的正面违反,而且是比 declared ≠ enforced 更糟的一档:不只是"schema 说有、运行时不查",而是构建期主动帮作者把这个能力加上去(extractor 认 ctx.crypto.hash 这个模式),等于系统在鼓励作者走进一条死路。

唯一的记录是 content/docs/automation/hook-bodies.mdx 里一句 _(not yet wired)_ 的表格备注 —— 而 spec、extractor、类型三处都不带任何标记。搜遍 issue 库,这个缺口零跟踪。

跟 #4345 / #4271 / #4001 是同一族(声明的能力没有兑现),区别是这次连"静默"都算不上 —— 它会响亮地抛错,只是抛在运行时而不是作者时。

两条路,需要决策

A. 实现。 在 installCtx 里装 ctx.crypto.hash,用 crypto.subtle.digest(WebCrypto,边缘运行时都有,符合"纯 JS、可跑边缘"的约束)。

  • ScriptContext.crypto.hash 的签名已经写好:(algo: string, data: string | Uint8Array) => Promise<string>
  • 需要走 installApiMethod 那套 deferred-promise 通道(它是 async)
  • 需要定 algo 白名单(sha-256 / sha-512?)和返回编码(hex?base64?)—— 签名说 Promise<string> 但没说哪种
  • 成本:中等,一个 host 函数 + 能力门 + 测试

B. 裁掉。 从 HookBodyCapability 移除 'crypto.hash',删掉 extractor 那条模式和 ScriptContext.crypto.hash 类型。

  • 这是移除一个可授权的 spec key,要走 spec-property-retirement 那一整套(ADR-0049 enforce-or-remove):tombstone / UNKNOWN_KEY_GUIDANCE、带 FROM→TO 的 breaking changeset、生成物基线、liveness 台账处置
  • 已经声明它的 body 会在授权时被拒 —— 需要迁移话术

倾向 A:签名已定、边缘可用的实现就在 crypto.subtle 里,而且哈希对"指纹 / 幂等键 / 脱敏"这类 hook 是真实需求;B 要付的退役成本比实现还高。但这是产品决策,不是我该独断的。

附带

无论走哪条,hook-bodies.mdx 那句 _(not yet wired)_ 要一起改 —— 它现在是唯一诚实的地方,但也是唯一一处,作者不会先读文档表格再写 body。

参考

Activity

  1. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    裁决(维护者 2026-08-02 委托,按四轴评估:长远合理性 / 防 AI 静默犯错 / 实际业务 / 不扩边界):remove —— 删除 crypto.hash 能力声明与构建期自动推断。

    • 业务事实:从未实现、调用即抛,而零投诉——这本身就是最强的活性证据:没有人需要它。
    • 边界:在沙箱里实现 crypto 是扩大沙箱能力面和安全审查面,无业务拉动不做。
    • 防 AI 犯错:构建期自动推断尤其危险——AI 写了 hash 调用,能力被自动声明,tsc 全绿,直到运行时才炸。删除声明与推断后,未知能力在作者时就红。
    • 长远:真需要时按能力准入流程重提,实现先行、声明随实现走(enforce-or-remove 的 enforce 路线留给有实现的那天)。

    v17 major 窗口内落地。


    Generated by Claude Code

  2. self-assigned this
    on Aug 2, 2026
  3. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    🔒 认领 + PM 裁定

    • 会话:session_015Br2xsJsczFsTR9bvbh2Ny(PM 循环,2026-08-02 19:50Z 轮)
    • 分支:claude/issue-4391-crypto-hash-sandbox
    • Worktree:objectstack-issue-4391

    ⚠️ 所有 agent 共用一个 GitHub 身份,assignee 无法区分认领人。若你读到本条且你的 session ID 不是上面那个,本单已被占,请另选。

    同时段协作提示:主 backlog 上另有一个 PM 会话(session_0176qgxgCXTJCUv4YFLtusP9)在推进 #4535 spec 双源清账。本轮已分工:packages/spec 全部归它。 本单按下述裁定不碰 spec。


    裁定:走 路线 A(实现),不走 B(裁掉)

    按 ADR-0049 enforce-or-remove,本单的两条路我按 PM 权限直接定 A,理由沿两轴:

    项目长远合理性 —— 功能不是「不该有」,是「只差实现」:签名 (algo, data) => Promise<string> 已在 script-runner.ts 定死,crypto.subtle.digest 在所有目标边缘运行时都有,哈希对指纹 / 幂等键 / 脱敏是 hook 的真实需求。B 要付的退役成本(tombstone、UNKNOWN_KEY_GUIDANCE、破坏性 changeset、生成物基线、liveness 台账、迁移话术)高于实现成本,且删掉的是一个作者真正想要的能力 —— 这是拿长期能力换短期账面干净。

    防 AI 犯错 —— 现状比普通的 declared ≠ enforced 更毒:构建期 extractor 主动认 ctx.crypto.hash 这个模式并替作者把能力加上,等于系统亲手把作者(尤其是 AI 作者)推进死路,而唯一的诚实记录是文档表格里一句 _(not yet wired)_ —— 没有作者会先读表格再写 body。A 让声明与实现对齐;B 则要靠「作者时报错 + 迁移话术」来教育,而 extractor 那条模式一旦删掉,写了 ctx.crypto.hash 的 body 会退化成静默失去能力,反而更难发现。

    附带的路线约束(非偶然):B 必须改 packages/spec/src/data/hook-body.zod.ts 的 HookBodyCapability 枚举 + tombstone + 生成物基线,而 spec 本轮归另一个 PM。A 完全不碰 spec。两轴已各自指向 A,这条只是确认没有反向拉力。

    维护者可否决:若你认为 crypto.hash 就该退休,本单回炉改走 B,届时排进 spec PM 的串行队列(限时:changeset pre exit 前)。


    本轮范围内的实现决策(dev 照此做,不要再问)

    签名已定但返回编码与 algo 白名单未定,我在这里定死,理由是「声明即强制、错误响亮」:

    1. algo 白名单:'sha-256' | 'sha-384' | 'sha-512'(WebCrypto digest 的原生集合,去掉 sha-1 —— 不给新代码提供弱哈希)。大小写与别名:同时接受 'sha256' 这类无连字符写法并规范化;其余一律抛,错误信息必须列出被支持的集合。⛔ 不做「未知 algo 回退到 sha-256」这类宽容降级 —— 那正是 AI 批量犯错被掩盖的温床。
    2. 返回编码:小写 hex 字符串。定了就写进 script-runner.ts 的类型注释与 hook-body.zod.ts 那行文档串旁的说明所在的文档页(⚠️ 文档串在 spec 里,本轮不要改 spec 文件;改 content/docs/automation/hook-bodies.mdx 即可)。
    3. 能力门:ctx.crypto.hash 必须与 randomUUID 一样受 capabilities 约束 —— 未声明 crypto.hash 的 body 调用它要拿不到这个函数(与现有能力门同形),而不是拿到一个会抛的函数。
    4. 异步通道:走 installApiMethod 的 deferred-promise 通道(它是 async,与 randomUUID 的同步装法不同)。
    5. _(not yet wired)_ 必须同批清掉(content/docs/automation/hook-bodies.mdx)—— 它是现在唯一诚实的地方,实现落地后它就成了新的谎言。

    验收

    • 正例:声明 crypto.hash 的 body 调 ctx.crypto.hash('sha-256', 'x') 返回正确 hex(对已知向量断言,别只断言「是个字符串」)
    • 能力门:未声明该能力的 body 拿不到该函数
    • 白名单:未知 algo 抛错且错误列出支持集合;'sha-1' 明确被拒
    • Uint8Array 与 string 两种入参都覆盖
    • 端到端:issue 正文那个 fingerprint_lead hook 从 os build 到实际写入全程通过
    • hook-bodies.mdx 的 _(not yet wired)_ 已删
    • ⛔ packages/spec/** 零改动(本轮硬约束);⛔ 不碰 content/docs/releases/

    Generated by Claude Code

  4. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    ⛔ 撤回上一条 PM 裁定 —— 维护者已于 09:50 裁定 remove,以维护者裁决为准

    上一条我写了「PM 裁定走路线 A(实现)」。那条作废 —— 我在写它之前没有先读本单评论,而 09:50 的维护者裁决(本页第一条)已经定了 remove:删除 crypto.hash 能力声明与构建期自动推断。

    维护者的理由直接驳掉了我那条推理里最实的一环:

    • 我说「哈希是真实需求」—— 裁决指出从未实现、调用即抛、零投诉,这本身就是最强的活性证据:没有人需要它。我把「签名已写好」误读成了需求信号,那只是声明面自己长出来的,不是业务拉动。
    • 我说「B 的退役成本高于实现成本」—— 只算了工时,漏了裁决点名的那项:在沙箱里实现 crypto 是扩大沙箱能力面与安全审查面,那是长期成本,不是一次性的。
    • 防 AI 犯错这一轴上我们结论相反,而裁决是对的:构建期自动推断是危险的放大器,删掉声明与推断后未知能力在作者时就红;实现它只是把炸点从运行时挪走,却永久扩大了能力面。enforce 路线留给「有实现的那天」,声明随实现走 —— 这才是 ADR-0049 在本单的正确读法。

    后果:本单本轮不派,退回队列

    remove 路线必须改 packages/spec/src/data/hook-body.zod.ts 的 HookBodyCapability 枚举,并连带 tombstone / UNKNOWN_KEY_GUIDANCE / 破坏性 changeset / 生成物基线 / liveness 台账。而本轮主 backlog 上另有 PM 会话(session_0176qgxgCXTJCUv4YFLtusP9)在串行推进 #4535 的 spec 双源清账(C12 #4703 在飞、C14 #4691 待派),packages/spec 整包归它,并发 spec PR 当天已实测造成三次冲突返工 + 一次合并队列 MERGE_CONFLICT 弹出。

    因此:撤销认领,移除 pm:dispatched,保留 pm:queue,由 spec 串行车道接手。

    ⏰ 给排期的人一个限时提醒

    本单是 v17 限时:@objectstack/spec 现为 17.0.0-rc.1,.changeset/pre.json 仍是 mode: pre / tag: rc。changeset pre exit 一旦执行,这个破坏性移除就只能等 v18,而 17.x 整个生命周期里作者写 crypto.hash 仍会拿到「构建通过、运行时抛」。裁决原文也写了「v17 major 窗口内落地」。建议它排在 C14(#4691)之后、关窗之前,优先级高于 v18 那批。

    实施时注意 extractor 那条模式(packages/cli/src/utils/extract-hook-body.ts:52)与 ScriptContext.crypto.hash 类型(packages/runtime/src/sandbox/script-runner.ts)要同批删,否则会退化成「静默失去能力」——那比现在更难发现。content/docs/automation/hook-bodies.mdx 那句 _(not yet wired)_ 同批清理。


    Generated by Claude Code

  5. removed their assignment
    on Aug 2, 2026
  6. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    🔒 认领(协议线 PM 接手 spec 车道):PM 会话 session_0176qgxgCXTJCUv4YFLtusP9,分支 claude/issue-4391-crypto-hash-retire。

    接手依据:另一 PM 会话(session_015Br2xsJsczFsTR9bvbh2Ny)已于 2026-08-02 19:54Z 撤回认领并退回队列,理由是 remove 路线必须改 packages/spec(该轮 spec 整包归本会话)。本会话的 spec 双源清账已于今日收官(基线 52 → 0,#4535 已关),spec 车道现可承接本单。

    方向以维护者 09:50Z 裁决为准:remove —— 删除 crypto.hash 能力声明与构建期自动推断。上游那条「PM 裁定走路线 A」已由其作者自行撤回,不再有效。

    派发前快检(09:5xZ):.changeset/pre.json 仍 mode: pre / tag: rc —— v17 限时成立,窗口一关这个破坏性移除就只能等 v18,而 17.x 全生命周期内作者写 crypto.hash 仍会「构建通过、运行时抛」。

    区域:spec/data/hook-body.zod.ts + cli/utils/extract-hook-body.ts + runtime/sandbox/script-runner.ts + docs/automation/hook-bodies.mdx;与在飞四单(#4657、#4793、#4756、#4709)及 #4001 重测均不相交,可并行实施,入队按 PM 指定顺序。


    Generated by Claude Code

  7. self-assigned this
    on Aug 3, 2026
  8. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    内容验收通过 → PR #4871;待同步 main 后入队

    PM 对分支尖 e6b31251 独立复核(不采信自报),实质合格:

    入队前差一步:分支尚未含 origin/main(61cc079e),而 #4841(#4793)与 #4852(#4001 重测)正在队列消化、都动 spec 生成物。已指示实施 agent 按标准协议同步(merge → 生成物取 main 版整体重生成 → 核对条目并存与删除零复活 → 重验 → push),回报后 PM un-draft + auto-merge。

    范围外发现 #4868(os-regen 驱动存的是上一个装过依赖的 worktree 绝对路径,该 worktree 一删全容器生成物合并驱动即 MODULE_NOT_FOUND,--self-test 照不出)已按 Prime Directive #10 单独归档、未夹带 —— 处置正确。


    Generated by Claude Code

  9. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    ✅ 同步完成、验收通过 → PR #4871 已转正 + auto-merge

    PM 对分支尖 03bc8c3d 独立复核:

    内容验收(四层删除为真、枚举值退役形态判定、conversion 分侧论证、runtime 类型 pin 休眠的诚实自陈与可执行替代)见前评。

    本单是 v17 协议线 ADR-0049 队列的收官单之一;剩余 #4834(plugin-runtime 家族)在飞。


    Generated by Claude Code

  10. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    ✅ 实施完成 —— draft PR #4871

    分支 claude/issue-4391-crypto-hash-retire,会话 session_0176qgxgCXTJCUv4YFLtusP9。方向按 09:50Z 维护者裁决:remove(19:52Z 那条「走路线 A/实现」已由其作者于 19:54Z 自行撤回,本 PR 未采信)。

    四层声明同批删除

    层 位置 处置
    capability token spec/data/hook-body.zod.ts 枚举 删值(6 → 5)
    文档串 同文件 JSDoc 删
    构建期推断 cli/utils/extract-hook-body.ts 删模式 + 两处联合类型 + @capabilities 白名单(共 4 处,本 issue 原只点了 1 处)
    运行时类型 runtime/sandbox/script-runner.ts 删 crypto.hash 成员

    quickjs-runner.ts 的 randomUUID 一字未动。另清理 hook-bodies.mdx 那句 _(not yet wired)_(移除后它会变成新的谎言)+ 两个 skills 文件(裸名扫描扫出来的,不在原范围清单里)。

    退役形态:枚举值收窄 ≠ key 移除

    retiredKey() 墓碑 / UNKNOWN_KEY_GUIDANCE / 基线删行三条常规路都不适用 —— capabilities 这个 key 依然活着、依然被强制,上墓碑会把整个键打死。改由枚举 error map + issue.input 键控承载处方(object.managedBy: 'system' 先例),且只对 crypto.hash 这一个拼写给退役话术:写错成 crypto.hsah 的人仍拿 zod 的合法值列表,告诉他"你的值被退役了"是误导(单独有 pin)。

    门禁实报里最该被看见的一条:四张 ratchet 对枚举值收窄全部不可见(逐字节零差异)。也就是说没有任何基线能兜住这次移除,兜住它的只有新增的 9 条 pin —— 四次 sabotage 已实跑证明"复活即红"(复活枚举值 / 复活 extractor 模式 / 往 VM seam 装回 hash / 拆掉 D3 接线)。

    ADR-0087 conversion:需要

    capability token 躺在作者源 capabilities: [] 与 sys_metadata 行里 → 属 #4734 那一侧(#4767/#4783/#4616 退的是导出名/运行时描述符,无作者源可改写)。注册 D2 hook-body-crypto-hash-removed(走 hooks[] + actions[])+ D3 protocol-17,retiredFromLoadPath: true。

    刻意不做:只剥 token,不碰 body 里那行 ctx.crypto.hash(...) 死调用 —— 顺手"修好"会拿走作者唯一的死代码信号。

    迁移话术

    两个都删:capabilities 里的 token + 对应的 ctx.crypto.hash(...) 调用(它从未返回过值,今天能跑的代码没有一行依赖它)。os migrate meta --from 16 自动剥 token,死调用作者自删。确需哈希→在 host 侧做(Connector recipe / 引擎侧 hook);沙箱内哈希须按能力准入流程重开,实现先行、声明随实现走。

    三仓扫描

    objectui(785b8a5)/ cloud(5df2c69)均 0 命中,空结果已用 capabilities 反查做对照验证(排除假空)。examples/ 零命中。未发现任何真实运行时实现或消费方。

    验证

    check:generated 8/8、check:dual-source-exports 0、liveness/empty-state 等源码审计全 PASS;spec 7365 / cli 588 / runtime 1094 用例全绿;全仓 typecheck 122/122 exit=0。v17 窗口核过两次,pre.json 仍 mode: pre / tag: rc,本破坏性移除赶在窗口内。

    范围外发现

    #4868 —— merge.os-regen.driver 指向已删 worktree 的绝对路径,全容器生成物合并驱动因此 MODULE_NOT_FOUND(两次 merge 均撞到)。已单独归档、未在本 PR 顺手改。

    PR 保持 draft,un-draft / 入队交 PM。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions