Skip to content

fix(plugin-security): explain posture 证据对齐 enforcement 派生(消除标签漂移) - #2949

Merged
os-zhuang merged 1 commit into
mainfrom
claude/authz-secfollowup-explain-posture
Jul 15, 2026
Merged

fix(plugin-security): explain posture 证据对齐 enforcement 派生(消除标签漂移)#2949
os-zhuang merged 1 commit into
mainfrom
claude/authz-secfollowup-explain-posture

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

背景(Security review 低危 — posture 标签漂移)

explain-engine 的 derivePosture(context)松名字匹配作 posture 证据:

  • permissions.includes(ADMIN_FULL_ACCESS) — 纯名字,不校验非作用域
  • positions.includes('org_owner' / 'org_admin') — 读 better-auth 角色岗位。

而 enforcement(packages/core/src/security/resolve-authz-context.ts)的派生更严:

  • hasPlatformAdminGrant = admin_full_access非作用域(organization_id == null)的 user grantsys_user_permission_set);
  • TENANT_ADMINorganization_admin能力ctx.permissions.includes(ORGANIZATION_ADMIN)),不读角色岗位(ADR-0095 D3:角色是 provisioning 来源,非裁决输入)。

后果:一个作用域admin_full_access grant(或 org_owner/org_admin 角色)会让 explain 面板给运维显示偏高的 PLATFORM/TENANT_ADMIN 标签。这不改变 explain 的 allow/deny verdict(verdict 来自复用的 enforcement filter),只是 posture 标签漂移。

修法(让 explain 的 posture 走与 enforcement 同一份证据)

  1. 优先直接消费 ctx.posture:principal 经完整 resolveAuthzContext 时已带 enforcement 派生的 rung,derivePosture 逐字返回 → 结构上不可能漂移。
  2. 回退路径(explain 用 buildContextForUser 自建 context,不经完整 resolveAuthzContext,故无 posture):复制 enforcement 的非作用域 grant 判定——
    • buildContextForUser 现按与 hasPlatformAdminGrant逐字节一致的规则(名字 admin_full_access 且该 user grant 行 organization_id == null 且在有效期内)计算并挂出 hasPlatformAdminGrant
    • derivePosturehasPlatformAdminGrant投影出的 platform_admin 内建岗位(该岗位本身只由该 grant 投影而来 — ADR-0068 D2)判 PLATFORM_ADMIN;以 organization_admin能力TENANT_ADMIN不再org_owner/org_admin 角色岗位。
  3. 保留 explain 特有的 guest → EXTERNAL(enforcement floor 是 MEMBER),且置于最前(即便挂了 posture 也先落 EXTERNAL)。

范围:只改 explain 的 posture 标签证据 —— 不改 explain 的实际 verdict、不改 enforcement。#2947 已跟踪「posture 未 plumb 进 enforcement context」的更广缺口;本 PR 不做 plumbing,只让 explain 侧派生与 enforcement 派生一致。

测试(实测)

pnpm --filter @objectstack/plugin-security test423 passed / 18 files(含 authz-matrix-gate、conformance 等)。explain-engine.test.ts 33 passed,新增/更新:

  • ctx.posture → 逐字返回(含"显式 posture 覆盖松回退")。
  • 作用域admin_full_access(名字在 permissions、无 unscoped grant、无 platform_admin 岗位)→ MEMBER,不再误标 PLATFORM_ADMIN。
  • hasPlatformAdminGrant: true → PLATFORM_ADMIN;投影 platform_admin 岗位 → PLATFORM_ADMIN。
  • org_owner/org_admin 角色岗位(无 organization_admin 能力)→ MEMBER(漂移移除);organization_admin 能力 → TENANT_ADMIN。
  • guest/anonymous → EXTERNAL(即便挂了 posture)。
  • buildContextForUser非作用域 admin_full_access user grant → hasPlatformAdminGrant: true作用域false(名字仍进 permissions,但不再赋 platform_admin posture)。

tsc 对改动文件无类型错误(仓库既存的 @objectstack/platform-objects/pages DTS 缺失与本改动无关,位于未触及的 security-plugin.ts)。

关联 #2920,跟踪 #2947

🤖 Generated with Claude Code

https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs


Generated by Claude Code

Security review 低危项。explain-engine 的 derivePosture 之前用松名字匹配作证据
(permissions.includes(ADMIN_FULL_ACCESS) 不校验非作用域 + positions org_owner/org_admin
读 better-auth 角色),比 enforcement(hasPlatformAdminGrant:要求非作用域
admin_full_access user grant;TENANT_ADMIN 用 organization_admin 能力)更松,可能让
explain 面板显示偏高的 posture 标签(作用域 org-admin grant 被误标 PLATFORM/TENANT_ADMIN)。
- 优先直接消费 ctx.posture(principal 经完整 resolveAuthzContext 时逐字返回,结构上不可能漂移)。
- 回退(buildContextForUser 自建 context):复制 enforcement 的非作用域 grant 判定——
buildContextForUser 现按与 hasPlatformAdminGrant 逐字节一致的规则(admin_full_access 且
organization_id==null 的 active user grant)计算并挂出 hasPlatformAdminGrant;derivePosture
以此 + 投影出的 platform_admin 内建岗位判 PLATFORM_ADMIN,以 organization_admin 能力判
TENANT_ADMIN,不再读 org_owner/org_admin 角色岗位(ADR-0095 D3)。
- 保留 explain 特有的 guest→EXTERNAL 底并置于最前。
只改 posture 标签证据,不改 explain verdict、不改 enforcement。#2947 更广缺口不在范围。关联 #2920。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
@vercel

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 15, 2026 4:35am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 15, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 15, 2026 04:33
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security.

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

  • content/docs/getting-started/cli.mdx(via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx(via packages/plugins/plugin-security)
  • content/docs/permissions/authorization.mdx(via packages/plugins/plugin-security)
  • content/docs/permissions/explain.mdx(via @objectstack/plugin-security)
  • 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)
  • content/docs/plugins/packages.mdx(via @objectstack/plugin-security)
  • content/docs/releases/implementation-status.mdx(via @objectstack/plugin-security)
  • content/docs/ui/audience-based-interfaces.mdx(via packages/plugins/plugin-security)
  • content/docs/ui/dashboards.mdx(via @objectstack/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.

@os-zhuang
os-zhuang merged commit 29a4c90 into mainJul 15, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/authz-secfollowup-explain-posture branch July 15, 2026 04:40
os-zhuang added a commit that referenced this pull request Jul 15, 2026
…2956)
The ADR-0095 D2 posture ladder is derived once by the shared authz resolver
from capability grants, but both entry points that build the ExecutionContext
(rest-server, runtime resolveExecutionContext) dropped it — so any
enforcement-side reader of context.posture always saw undefined (the same drop
that forced the explain layer to re-derive it, #2949).
- spec: ExecutionContextSchema gains an optional `posture` field (reuses
AuthzPostureSchema; no api-surface change — schema tracked as a const).
- rest + runtime: plumb authz.posture through when present.
- tests: assert PLATFORM_ADMIN / MEMBER are carried and guest stays unset.
Additive and behavior-preserving: no enforcement decision consumes posture yet.
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
Co-authored-by: Claude <noreply@anthropic.com>
@github-actionsgithub-actionsBot mentioned this pull request Jul 15, 2026
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
…ole coverage
The previous revision under-covered the line in three ways, now fixed:
1. 15.0.0 was missing everything outside the ADR-0095 theme from the
14.8.0..15.0.0 range: the strict view/page schema BREAKING change
(ADR-0089 D3a, #2943) with its migration, the per-row import automation
chain + skipAutomations + runAutomations-default-ON behavior change
(#2922), the sys_position/sys_capability system-row write guardrail
(#2930), the bidirectional visibility lint (#2931), and the explain
posture-label alignment (#2949).
2. 15.0.0 had NO Console section at all, despite bundling the objectui 14.0
major (13.2.0→14.0.0, ~73 commits — first release-page disclosure since
much of it rode unversioned in the 14.8.0 pin): the ADR-0057 ChatDock
consolidation (including its breaking cleanup #2475), the Gantt batch,
lists/forms/auth/Studio/i18n enhancements, two security fixes
(#2485/#2410), and the six early-14.1 commits the 15.0.0 pin picked up.
3. The 15.1.0 sections were over-compressed; every domain is now expanded to
its full changeset detail, and the Console 14.1 section covers all eight
areas of the 94-commit range instead of seven bullets.
465 → ~1000 lines; structure mirrors v14.mdx (per-minor annotations inside
one major page, per docs/releases-maintenance.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
…ole coverage (#3082)
* docs(releases): expand v15 page to full 14.8→15.0→15.1 backend + Console coverage
The previous revision under-covered the line in three ways, now fixed:
1. 15.0.0 was missing everything outside the ADR-0095 theme from the
14.8.0..15.0.0 range: the strict view/page schema BREAKING change
(ADR-0089 D3a, #2943) with its migration, the per-row import automation
chain + skipAutomations + runAutomations-default-ON behavior change
(#2922), the sys_position/sys_capability system-row write guardrail
(#2930), the bidirectional visibility lint (#2931), and the explain
posture-label alignment (#2949).
2. 15.0.0 had NO Console section at all, despite bundling the objectui 14.0
major (13.2.0→14.0.0, ~73 commits — first release-page disclosure since
much of it rode unversioned in the 14.8.0 pin): the ADR-0057 ChatDock
consolidation (including its breaking cleanup #2475), the Gantt batch,
lists/forms/auth/Studio/i18n enhancements, two security fixes
(#2485/#2410), and the six early-14.1 commits the 15.0.0 pin picked up.
3. The 15.1.0 sections were over-compressed; every domain is now expanded to
its full changeset detail, and the Console 14.1 section covers all eight
areas of the 94-commit range instead of seven bullets.
465 → ~1000 lines; structure mirrors v14.mdx (per-minor annotations inside
one major page, per docs/releases-maintenance.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(releases): note post-14.1 fixes carried by the final Console pin (#2615/#2617/#2619/#2620/#2621/#2623)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(releases): add v15 to the releases index; mark v14 line final at 14.8.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
…nt path, no explicit deny (#3109)
Follow-through on ADR-0095: the derived posture rung is carried on the
ExecutionContext since #2956 but no enforcement decision reads it — each tier
decision re-derives principal tier from capability bits at its own site, the
divergence class behind #2946 Finding 2 (org admin crossed the tenant wall)
and #2949 (explain/enforcement posture split).
- D1: posture becomes the single tier-adjudication input (Layer 0 exemption,
Layer 1 short-circuit tier, explain); capability probe demoted to narrowing
fallback; behavior-preserving behind an equivalence gate in the authz matrix.
- D2: normative posture → layer action map with six invariants (TENANT_ADMIN
never crosses Layer 0; enforcement and explain read the same carried value;
posture selects tier, never side; never bypasses system-only write guards).
- D3: EXTERNAL rung activation chain (audience:'external' → derivePosture →
explicit-shares-only injection), matrix cells land now, implementation
demand-gated on an external principal type (portal track).
- D4: explicit deny/muting rejected as an adjudication primitive; needs mapped
to capability gates / FLS / admission / validity windows; assembly-time
muting reserved to a future permission-set-groups ADR.
Composes with ADR-0096 (admission vs tiering — orthogonal axes). Number 0099
verified free of open-PR claims before push.
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
Co-authored-by: Claude <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/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude