Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/adr-0095-d1-tenant-layer-0.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@objectstack/plugin-security": minor
"@objectstack/plugin-security": major
---

ADR-0095 D1: tenant isolation is now **Layer 0** — an independent, always-first,
Expand Down
2 changes: 1 addition & 1 deletion .changeset/authz-2937-insert-tenant-guard.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'@objectstack/plugin-security': minor
'@objectstack/plugin-security': major
---

fix(plugin-security): #2937 — Layer 0 insert post-image 租户检查(伪造 organization_id 的用户 insert 现被拒)
Expand Down
2 changes: 1 addition & 1 deletion .changeset/authz-tenant-write-wall-fix.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'@objectstack/plugin-security': minor
'@objectstack/plugin-security': major
---

fix(plugin-security): 堵跨租户 UPDATE 写 + org_admin 越 private 租户对象墙(security)
Expand Down
2 changes: 1 addition & 1 deletion content/docs/releases/meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"title": "Releases",
"icon": "Tag",
"pages": ["index", "v14", "v13", "v12", "v9", "implementation-status"]
"pages": ["index", "v15", "v14", "v13", "v12", "v9", "implementation-status"]
}
113 changes: 113 additions & 0 deletions content/docs/releases/v15.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: v15.0.0
description: Authorization kernel chain (ADR-0095) — tenant isolation becomes an independent Layer 0, a monotonic posture ladder derived from capabilities, cross-tenant write hardening, and record-grained access explanation. Breaking changes affect multi-org deployments only.
---

**Authorization kernel hardening (ADR-0095) + supporting features (tracking framework#2920).**
This release restructures the authorization hot path: tenant isolation moves out
of the business-RLS pass into an independent, always-first **Layer 0**; principal
tiering becomes an explicit, capability-derived **posture ladder**; and the tenant
write wall is closed on the update path. Several **behavior changes affect
multi-org (`tenancy.mode = 'multi'` + `@objectstack/organizations`) deployments
only** — single-org and physically-isolated (env-per-database, ADR-0002) deployments
are unaffected.

Read this before upgrading a multi-org or enterprise-hierarchy deployment.

## Breaking changes

### Multi-org deployments only

- **Cross-tenant read leak closed (Layer 0).** A permissive business RLS policy
(e.g. `status == 'public'`) can no longer OR-widen tenant scope. Tenant isolation
is now AND-composed as the outermost filter (`Layer0(tenant) AND Layer1(business RLS)`);
a foreign-org row a business policy matched is now invisible.
- **Member by-id writes narrow to owner-only.** The previous OR-merge silently
widened `owner_only_writes` back to org-wide, so a member could by-id update/delete
*any* record in their org. Writes are now owner-scoped as authored. **Migration:**
if your deployment intentionally relied on members editing each other's records
org-wide, grant an explicit per-object edit permission set (position-distributed)
where that is wanted — the baseline `member_default` no longer permits it.
- **Cross-tenant writes via `organization_id` blocked (insert + update).** A user
could previously plant a row into another tenant by supplying a forged
`organization_id` on insert, or by reassigning it on update. Both are now denied
for user contexts (Layer 0 post-image check, symmetric across insert/update/bulk);
`organization_id` is effectively immutable outside system context. Default writes
(no `organization_id`) and system-context writes (import, migration, per-org seed
replay) are unaffected.
- **Org admins no longer cross the tenant wall on `private` objects.** The Layer 0
cross-tenant exemption is now gated on a genuine platform-admin capability
(`manage_metadata` / `manage_platform_settings` / `studio.access` / `manage_users`),
not on the `viewAllRecords` / `modifyAllRecords` super-bits that `organization_admin`
also holds. On `access.default: 'private'` tenant objects an org admin is now walled
to their own organization (previously unfiltered). Genuine platform admins are
unaffected; the super-bits continue to drive only the in-org Layer 1 business-RLS
short-circuit (TENANT_ADMIN semantics unchanged).
- **No-active-org writes fail closed.** A write by a principal with no active
organization on a tenant object is now denied.
- **Seeded `tenant_isolation` policy retired.** The wildcard `tenant_isolation`
RLS policy is removed from the `organization_admin` / `member_default` /
`viewer_readonly` sets (isolation is enforced by Layer 0 instead). The
`_self` / `_org` identity-table carve-outs and `owner_only_writes/deletes` are
unchanged; customized seeded sets keep their overlays (ADR-0094).

### Enterprise hierarchy RLS (`@objectstack/security-enterprise`)

- **`unit` scope anchors to the position's business unit.** `unit` / `unit_and_below`
now prefer `sys_user_position.business_unit_id` (the position's BU) over
`sys_business_unit_member` membership. **Deployments that do not populate
`sys_user_position.business_unit_id` see no change** (every user falls back to
membership). Once anchors are populated, a user's `unit` visibility narrows to
their position BU; multiple positions union; expired/inactive positions fall back
to membership.

## Non-breaking hardening (same effective visibility)

- **RLS safety nets recognize canonical `==` (#2936).** The field-existence /
tenancy-disabled nets were inert for `==`-form policies; now recognized. Only
visible effect: on column-less system tables a member's by-id write is now an
explicit fail-closed deny rather than a phantom-column filter — same result.
- **Self-delegation position anchors are subtree-constrained.** A delegated position's
`business_unit_id` must fall within the delegator's own effective anchor — closing a
lateral-visibility escalation the position-anchor change would otherwise have opened.
- **Hierarchy resolver queries are org-scoped (defense in depth).** Owner-id resolution
now filters by organization directly, and private hierarchy-scoped objects that lack
an `organization_id` column fail closed at boot.

## New features (additive)

- **A3** — the `sys_user` record page gains **Permission Sets** (direct grant) and
**Business Units** assignment tabs.
- **A4** — record-level provenance vocabulary unifies to **platform / package / admin**
across `sys_capability` / `sys_permission_set` / `sys_position`, surfaced as a badge;
legacy values self-heal at boot (no destructive migration).
- **A5** — package-level capability declaration API (`defineCapability`): a package's
own capabilities flow into the registry with package provenance.
- **B2/B4** — an explicit, monotonic **posture ladder**
(`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL`) derived from capability grants
(never the identity provider's admin designation); an additive, derived, explainable field. Enforcement is
behavior-preserving.
- **C2** — `security.explain` extends to **record granularity**: explain why a specific
record is visible/invisible to a user, layer by layer (permission set → position →
sharing → row rule → effective row filter), with Layer 0/1 and posture surfaced in the
Studio Access panel.
- **C1** — hierarchy RLS enterprise package reaches GA (subtree/reporting-chain caching,
customer docs, license gate).

## Upgrade checklist

- **Multi-org:** review any workflow that relies on members editing each other's records
org-wide (grant an explicit edit permission set) and any integration that supplied a
cross-tenant `organization_id` on user-context writes (route it through system context
or a dedicated endpoint).
- **Enterprise hierarchy:** the upgrade itself is inert; plan BU-anchor data before you
begin populating `sys_user_position.business_unit_id`, since `unit` visibility then
narrows to the position BU.
- **Before production:** a multi-org / hierarchy / tenant-write-guard **enterprise
end-to-end smoke test in a licensed environment is still owed** — open-core CI cannot
exercise the multi-org path.

## References

ADR-0095 (kernel chain) · ADR-0090 Addendum (position-anchor) · ADR-0094 (overlay
preservation) · framework#2920 (tracking).
Loading