Skip to content

Restrict guest visibility to accessible spaces - #65

Draft
ArtyomSavchenko wants to merge 13 commits into
developfrom
person-visibility
Draft

Restrict guest visibility to accessible spaces#65
ArtyomSavchenko wants to merge 13 commits into
developfrom
person-visibility

Conversation

@ArtyomSavchenko

@ArtyomSavchenko ArtyomSavchenko commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces a consistent security layer for restricted workspace roles:

  • ReadOnlyGuest
  • DocGuest
  • Guest
  • future roles below AccountRole.User

It closes data-discovery gaps in shared/system spaces and aligns read, full-text search, and write handling around declarative row-visibility policies.

Problem

Restricted accounts could access or discover records outside their intended scope:

  • Person / Employee records live in the shared contact.space.Contacts system space.
  • Sensitive records in core.space.Workspace were not protected by ordinary space membership:
    • Collaborator
    • love.class.MeetingMinutes
    • love.class.RoomInfo
    • hr.class.Request
    • notification.class.PushSubscription
    • guest.class.PublicLink
  • Disabling an application in Guest permissions hid navigation but could still leave documents reachable through findAll or full-text search.
  • Known identifiers could bypass ownership checks in contexts where an identifier is not a sufficient authorization proof.
  • Guest-created documents could bypass the class-level write gate.

Security model

Restricted-role threshold

roleOrder is now the single source of truth for identifying a restricted account:

isRowLevelRestricted(role)

This avoids duplicating explicit lists of guest roles across authorization code.

Layer 1 — class/action access

Write operations use the existing permission-group configuration and TxAccessLevel metadata:

  • role-specific ModulePermissionGroup / ClassPermission configuration is cached;
  • the cache is invalidated when permission documents change, including nested TxApplyIf;
  • TxMixin is checked as an update operation instead of being unconditionally allowed;
  • a document’s createdBy value no longer bypasses class-level access checks.

Layer 2 — row visibility

core.mixin.RowVisibility declares how a restricted role may access records in shared or system spaces:

  • ownerField
  • linkedViaRecord
  • spaceMember
  • denyAll
  • publicReadable

Policies are registered with the affected class model and inherited through the hierarchy.

allowKnownIdBypass remains explicit per class. It is disabled for full-text search and mutation checks, where a caller-supplied identifier must not be treated as proof of authorization.

Read protection

findAll

  • Person and Employee discovery is restricted to people sharing a real space with the caller.
  • Classes with a declared RowVisibility policy are narrowed to permitted records.
  • Classes without a policy are denied in main/system spaces, while normal real-space membership continues to work.
  • Disabled application spaces are excluded regardless of a direct query.

searchFulltext

  • Applies the same person visibility rules.
  • Rechecks each result against RowVisibility without a known-id bypass.
  • Filters objects from applications disabled for the caller’s role.

Declared policies

Class Policy
core.class.Collaborator Own collaborator account
love.class.MeetingMinutes Linked collaborator record
love.class.RoomInfo Deny open browsing
hr.class.Request Own attachedTo person
notification.class.PushSubscription Own user account
guest.class.PublicLink Session linkId; no known-id bypass

Guest membership behavior

New named guests no longer inherit the anonymous read-only account’s space membership. Access is granted only through explicit invitation or autoJoinForRoles.

Tests

Added and updated regression coverage for:

  • row-level policies on sensitive shared-space classes;
  • denial of undeclared shared-space classes;
  • full-text results not acting as a known-id bypass;
  • public-link isolation;
  • class-level write access not being bypassed by document ownership;
  • permission-cache invalidation.

Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
@ArtyomSavchenko
ArtyomSavchenko marked this pull request as ready for review August 1, 2026 03:43
@ArtyomSavchenko
ArtyomSavchenko marked this pull request as draft August 13, 2026 05:24
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant