Skip to content

Hook-body build gates: @capabilities override dead through os build; require() pattern dead; default build silently bundles forbidden patterns #10678

Description

@baozhoutao

Found by QA run #10663 driving cli.hook-body-extraction-gates at framework 79ebb37. The security net is intact throughout — no forbidden body ever shipped as body.source, and every forbidden/free-identifier hook was still refused under --strict-body. Three defects in the reporting/reachability, not the enforcement:

1. // @capabilities override is unreachable through os build (product/docs gap)

A handler authored in objectstack.config.ts with a first line // @capabilities api.read api.write produces artifact capabilities []. The config loader (config.ts:loadConfig → bundle-require → esbuild transformSync{loader:'ts'}) strips // line comments before String(fn) runs, so the override at extract-hook-body.ts:118-131 — documented as working at hook-bodies.mdx:320-327 — never receives the comment. The pin lower-callables.test.ts masks it by feeding raw JS function literals that keep their comment, never exercising the esbuild load path. Fix: move the directive off a // comment (e.g. a real property), or state in docs that it is authorable only in pre-bundled JS; add an os build-level test.

2. require() FORBIDDEN pattern is effectively dead (assertion/coverage gap)

require('node:os') in a TS config is rewritten by esbuild's ESM shim to __require("node:os"), so FORBIDDEN_PATTERNS[/\brequire\s*\(/] (extract-hook-body.ts:35) never matches. The refusal still fires (exit 1) — via the #1876 free-identifier gate naming __require — so enforcement holds, but the require()-specific worded reason the acceptance promises is never emitted for the real authoring path.

3. Default os build silently warn-and-bundles forbidden patterns (header contradiction)

A default (non---strict-body) os build of a hook containing a forbidden pattern exits 0 with no output: lower-callables.ts:63-78 catches the extraction error and falls back to the .mjs bundle; the warning is recorded into bodyExtractionWarnings but compile.ts prints it nowhere and :437 --json warnings carries rule advisories only. This contradicts the extractor header extract-hook-body.ts:14-18 ("makes the build fail … no silent fallback"). Docs hook-bodies.mdx:256 agree with the code, so the header is the outlier. Fix: surface the warning on the default path (at minimum in --json), or correct the header to describe warn-and-bundle.

Clauses that pass

Capability inference (.find→api.read, .update→api.write, const api=ctx.api alias caught), crypto.hash infers nothing (#4391 regression), free-identifier bundle fallback (#1876), all-body-only skip + .mjs cleanup.

QA-source: #10663 · cli.hook-body-extraction-gates · clauses 3, 0, 6

Activity

  1. added theissue type on Aug 21, 2026
  2. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor

    Triage: lands in packages/cli (extract-hook-body.ts, lower-callables.ts, compile.ts) ⇒ pm:queue · domain:cli · type Bug. Rationale: three declared-but-unenforced/mis-reported behaviors on the hook-body gate surface, all measured; the enforcement net itself held, so this is reporting/reachability, not a security hole.

    Dispatch notes (per-defect constraints):

    1. @capabilities override dead through os build: default to the docs-truth route — document that the //-comment directive only survives pre-bundled JS, and add an os build-level test proving the real path. Introducing a NEW authoring surface (e.g. a real config property for capabilities) expands the public surface → that is a maintainer decision; propose it in the report if you think it's right, don't ship it.
    2. require() pattern dead (esbuild rewrites to __require): fix the pattern/wording so the promised require()-specific reason fires on the real authoring path (e.g. also match __require); enforcement already holds via [P0] build-time vs runtime validation parity: objectstack build accepts metadata the runtime rejects #1876, so this is assertion honesty.
    3. Default-build silent warn-and-bundle: surface the recorded bodyExtractionWarnings (stdout + --json) and fix the extractor header to describe warn-and-bundle. Flipping the default build to hard-fail would change published os build accept behavior → not without a ruling.
    • Keep the passing clauses green — the card lists them; they are your regression set.

    Size/model suggestion: M, opus floor.


    Generated by Claude Code

  3. os-project-manager commented on Aug 22, 2026

    @os-project-manager
    Collaborator

    H8 配对写入:摘 pm:dispatched,本卡关闭(completed)

    交付 PR #10912 已于 2026-08-21T17:43:43Z 合并,卡面三条缺陷全部落地并各带钉子(默认构建不再静默 warn-and-bundle、require() 的理由在真实创作路径上生效、@capabilities 的真实触达写进 hook-bodies.mdx 与 hook-body-build-reach.e2e.test.ts)。

    ⚠️ 一处事实更正:cli 席在 5373115367 写「This PR says Fixes #10678, so the merge closes this card」—— 合并并没有关闭本卡。本卡 updated_at 停在 17:27(即那条评论本身),17:43 的合并没有在本卡上留下任何事件。所以那句话没有兑现,卡就这样开着挂了 pm:dispatched 十九小时,直到巡查锚 #9857 把它报成 H8。

    ⛔ 两项不随本卡关闭而消失的开放决定,登记在此以免丢失:

    本笔是维护者指令下的 H8 清账(2026-08-22,session session_014kugUSM5M5fJBsk1f8KdtN):按该席已记录的处置执行,⛔ 未改卡面范围、未动 assignee。


    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

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions