Skip to content

feat(security): ADR-0090 D5/D9 suggested-audience-binding surface + confirm/dismiss API - #2746

Merged
os-zhuang merged 4 commits into
mainfrom
claude/loving-knuth-y1g4d3
Jul 10, 2026
Merged

feat(security): ADR-0090 D5/D9 suggested-audience-binding surface + confirm/dismiss API#2746
os-zhuang merged 4 commits into
mainfrom
claude/loving-knuth-y1g4d3

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

摘要

补上 #2696 中标记为"缺失框架表面"的 D5 安装建议确认能力:包权限集声明 isDefault: true 是安装时的建议("把此权限集绑定到 everyone 岗位"),此前服务端只在启动时把它读成 fallback 权限集名称——安装后既没有可查询的待确认建议列表,也没有确认/绑定端点。本 PR 提供两者,绝不自动绑定(D5 语义)。

plugin-security

  • 新增系统对象 sys_audience_binding_suggestion(数据 API 只读;package_id × permission_set_name × anchor 唯一;anchor 取值 everyone/guest,为 D9 的 guest 泛化预留,当前唯一生产来源是 isDefaulteveryone)。
  • 新增收敛式对账syncAudienceBindingSuggestions:同时读取启动声明的 stack 元数据和已安装包的 manifest(registry 在 installPackage 时同步更新,所以运行时 POST /api/v1/packages 安装后立即可见,无需重启);声明消失(卸载/升级去掉标志)→ 删除 pending 行;绑定在别处已建立(启动基线绑定、管理员手工绑定)→ 标记 confirmed(observed)。在启动、permission 发布物化之后、以及每次 list 调用时运行。
  • security 服务新增 listAudienceBindingSuggestions / confirmAudienceBindingSuggestion / dismissAudienceBindingSuggestion,三者都预先用租户级管理员(ADR-0066 超级用户通配符)门禁——受众锚点是租户级的(D12,委托管理员不可触碰)。
  • confirm 用调用者的执行上下文写 sys_position_permission_set(绝不 isSystem),因此 D5/D9 受众锚点门禁(高权限位拒绝)和 D12 委托管理门禁真实生效;另做一次 describeAnchorForbiddenBits 预检,让被拒时报错信息友好。本会话内刚安装、尚未物化的权限集,先经由与启动 seeder / 发布物化器相同的 provenance 检查 upsert(ADR-0086 D4)落库再绑定。

HTTP 表面(rest + runtime 双层)

hono 开发/生产栈的 /api/v1@objectstack/rest 提供,适配器部署走 runtime HttpDispatcher——两层都注册了路由,委托同一个 security 服务:

  • GET /api/v1/security/suggested-bindings?status=&packageId=
  • POST /api/v1/security/suggested-bindings/:id/confirm
  • POST /api/v1/security/suggested-bindings/:id/dismiss

未认证 401;服务端类型化错误映射 403/404/409;未装 plugin-security 时 501(rest)/503(dispatcher)。

测试与验证

  • suggested-audience-bindings.test.ts(18 个用例):对账的幂等/剪枝/observed-confirm、confirm 的物化+调用者上下文断言、高权限位早期拒绝、外包名冲突拒绝、404/409、非管理员拒绝等。
  • http-dispatcher.test.ts 新增 6 个路由用例;rest 218 个用例全绿。
  • pnpm turbo test --filter=...@objectstack/plugin-security --filter=...@objectstack/runtime 全绿(69 个任务)。
  • 真实环境端到端验证(fresh showcase,objectstack dev --seed-admin --fresh):匿名 list → 401;管理员 list → showcase 的第二个 isDefault 权限集(showcase_member_default)以 pending 出现;confirm → 200、bindingCreated: truesys_position_permission_set 行由调用者(非 system)创建;重复 confirm → 409。
  • CodeQL 高危告警(请求路径上的多项式回溯正则)已修复(改用 split('/').filter(Boolean))。

文档

content/docs/permissions/permission-sets.mdxisDefault 小节补充了新表面与端点说明。

关联

🤖 Generated with Claude Code

https://claude.ai/code/session_01DP13MuGwFWLcVxVtuA7rgq

…m/dismiss API (ADR-0090 D5/D9)
A package permission set declaring isDefault: true is an install-time
SUGGESTION to bind the set to the builtin everyone position — never
auto-bound. Until now the flag was only read at bootstrap as the
fallback-set name; after an install there was no way to see or act on
the suggestion (framework#2696 remaining item).
plugin-security:
- new sys_audience_binding_suggestion system object (read-only data API,
unique per package × set × anchor)
- convergent reconciler syncAudienceBindingSuggestions reading declared
stack metadata AND installed package manifests (a runtime
POST /api/v1/packages install is visible immediately); prunes pending
rows on uninstall, marks out-of-band bindings confirmed (observed);
runs at boot, after a permission publish, and on every list call
- security service gains listAudienceBindingSuggestions /
confirmAudienceBindingSuggestion / dismissAudienceBindingSuggestion,
all pre-gated on tenant-level admin (anchors stay tenant-level, D12);
confirm inserts sys_position_permission_set WITH the caller's
execution context so the D5/D9 anchor gate and D12 delegated-admin
gate enforce the write; not-yet-materialized sets are seeded through
the provenance-checked upsert (ADR-0086 D4)
runtime:
- GET /api/v1/security/suggested-bindings?status=&packageId=
- POST /api/v1/security/suggested-bindings/:id/confirm
- POST /api/v1/security/suggested-bindings/:id/dismiss
(401 unauthenticated; 403/404/409 from typed service errors; 503
without plugin-security)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP13MuGwFWLcVxVtuA7rgq
@vercel

vercelBot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 10, 2026 5:54am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 10, 2026
@github-actions

github-actionsBot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime.

25 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx(via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx(via @objectstack/rest)
  • content/docs/api/index.mdx(via @objectstack/rest, @objectstack/runtime)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx(via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx(via packages/runtime)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/cli.mdx(via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx(via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx(via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via packages/plugins/plugin-security, packages/rest)
  • content/docs/permissions/permissions-matrix.mdx(via packages/plugins/plugin-security)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/plugin-security)
  • content/docs/plugins/index.mdx(via @objectstack/plugin-security, @objectstack/rest)
  • content/docs/plugins/packages.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime)
  • content/docs/protocol/objectos/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/objectos/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx(via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime)
  • content/docs/ui/dashboards.mdx(via @objectstack/plugin-security)
  • content/docs/ui/role-based-interfaces.mdx(via packages/plugins/plugin-security)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…DR-0090 D5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP13MuGwFWLcVxVtuA7rgq
Comment threadpackages/runtime/src/http-dispatcher.ts Fixed
… polynomial-redos)
split('/')+filter(Boolean) already discards empty segments; the
^\/+|\/+$ regex over request-controlled input was flagged by CodeQL
as polynomial backtracking.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP13MuGwFWLcVxVtuA7rgq
…ace (ADR-0090 D5/D9)
The hono dev/production stack serves /api/v1 through @objectstack/rest,
not the runtime HttpDispatcher, so the suggested-bindings routes must be
registered there too (same pattern as sharing rules: a security service
provider injected by rest-api-plugin, resolveExecCtx + enforceAuth per
request, 501 when plugin-security is absent, typed 403/404/409 mapping).
Verified end-to-end against a fresh showcase run: anonymous list → 401;
admin list → the showcase's second isDefault set (showcase_member_default)
appears as a pending suggestion; confirm → 200 with the binding row
created by the caller (not system) in sys_position_permission_set;
re-confirm → 409.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP13MuGwFWLcVxVtuA7rgq
@os-zhuang
os-zhuang marked this pull request as ready for review July 10, 2026 08:02
@os-zhuang
os-zhuang merged commit bd39dc5 into mainJul 10, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/loving-knuth-y1g4d3 branch July 10, 2026 08:02
os-zhuang pushed a commit that referenced this pull request Jul 10, 2026
Both sides added a securityServiceProvider (mine for /security/explain,
main's for /security/suggested-bindings): kept one shared provider, kept
main's registerSecurityEndpoints (suggested-bindings) and renamed the
explain registrar to registerSecurityExplainEndpoints.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTubNprerCmJoU1VtUWA93
os-zhuang added a commit that referenced this pull request Jul 10, 2026
Ships the ADR-0090 v2 UI surface into the vendored console: spec-13
adoption + position relabel + external OWD dial + matrix OWD badges
(objectui#2369), the D5 suggested-audience-binding install prompt +
Access-pillar banner (objectui#2376, pairs with #2746), and the D6
'why can this user access?' explain panel (objectui#2375, pairs with
#2743). Console rebuilt and bundle canary verified at this sha.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@os-zhuang@github-advanced-security@claude