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

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 15, 2026 4:35am

Request Review

@github-actions github-actions Bot 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 main Jul 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>
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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants