How Cloudflare OS Manages Collaboration and Permissions for Gadgets

Cloudflare OS implements a permission-graph model in SharingManager that computes effective roles by traversing directed edges from the owner to collaborators, supporting both direct user invitations and share-link redemption while enforcing hierarchical access controls.

Cloudflare OS governs multi-user access to Gadgets through a sophisticated permission system defined in the cloudflare/cloudflare-os repository. Instead of simple access lists, the platform employs a directed graph of permission edges that dynamically calculates a collaborator's effective role whenever they open a workspace. This architecture ensures transitive permission propagation, safe revocation, and real-time session enforcement across distributed Durable Objects.

The Permission-Graph Architecture

The core permission logic lives in packages/workshop-backend/src/sharing.ts inside the SharingManager class. When a client opens a workspace, the Overseer (packages/workshop-backend/src/overseer.ts) queries this manager to determine the caller's effective role—the highest privilege reachable through valid permission edges.

The graph consists of two distinct edge types that record how access was granted:

  • User edge: Created when an owner or existing collaborator adds a user via the Share UI. Records the sharer (who added them), the role granted, and an optional note.
  • Share-link edge: Created when a user redeems a share-link. Stores the keyId of the link, with an implicit sharer reference to the link creator and the role defined in the link configuration.

Role Hierarchy and Constraints

Roles follow a strict hierarchy: use < build < owner. The addCollaborator method enforces that a caller can never grant a role higher than their own effective role. This constraint prevents privilege escalation and maintains the integrity of the permission graph.

Core Collaboration Workflows

Cloudflare OS handles five critical permission operations through the SharingManager API, each designed to maintain graph consistency and session safety.

Adding Collaborators with addCollaborator

When an owner or build-level collaborator invites a new user, the system creates a directed user edge linking the new profile to the caller. If an edge already exists, the manager upgrades it to the higher role.

// Owner or build-collaborator adds a user
await overseer.addCollaborator({
  caller: { profileId: "alice@example.com", isOwner: false },
  profile: await fetchProfile("bob@example.com"),
  role: "use",                     // Cannot exceed caller's effective role
  note: "Added for UI review",
});

This operation corresponds to SharingManager.addCollaborator at lines 98–115 of packages/workshop-backend/src/sharing.ts.

Share links mint unique secret keys that transient users can redeem. When a user opens a link containing a share key, redeemShareKey hashes the key, validates the ShareKeyRecord, and adds a share-link edge to the permission graph if the link is active.

// User opens a gadget via share link
await overseer.redeemShareKey({
  rawKey: location.hash.split("share=")[1]!,
  profileId: currentUserId,
  fetchProfile: () => fetchProfile(currentUserId),
});

The implementation resides in SharingManager.redeemShareKey (lines 23–38).

Computing Effective Roles

Before granting access, the Overseer determines the caller's capabilities. The open() method calls SharingManager.getEffectiveRole(profileId), which traverses the graph to find the highest reachable role. Based on this result, the system returns either the full OverseerClientInterface (for owner or build) or the restricted UseOverseerInterface (for use-only access).

// Internal check during Overseer.open()
const role = await sharingManager.getEffectiveRole("charlie@example.com");
if (role === "build") {
  // Grant full Overseer capabilities
} else if (role === "use") {
  // Grant limited UI-only capabilities
}

See the open implementation in packages/workshop-backend/src/overseer.ts (lines 55–63).

Revocation and Live Session Management

Removing a collaborator or revoking a share link deletes only the permission edges, not the underlying profile records. Because computeEffectiveRoles() recalculates access on every workspace open, changes propagate immediately to new sessions.

If a revocation affects active sessions, the workspace Durable Object is aborted, forcing all connected clients to reconnect and re-evaluate their roles. This ensures no stale permissions persist in memory.

// Owner removes a collaborator
await overseer.removeCollaborator({
  caller: { profileId: "owner@example.com", isOwner: true },
  profileId: "bob@example.com",
});

The restart logic and edge deletion are handled in removeCollaborator (lines 384–400).

Observer Verification for Third-Party Resources

Non-owner collaborators (use and build roles) are treated as observers. Before reading bound resources (such as Google Drive files or GitHub repositories), the system executes ensureObserver to verify that the collaborator's third-party account possesses the required ACLs. This binding-aware check prevents unauthorized data access even when the Gadget permission graph grants entry.

The observer policy is documented in docs/observers.md.

Key Design Principles

The Cloudflare OS permission system prioritizes safety, reversibility, and clean architecture through three specific design patterns.

Lazy Revocation

Rather than purging records immediately, computeEffectiveRoles() ignores invalid or removed edges while keeping the underlying data intact. This makes undoing accidental removals trivial: re-adding the edge restores the entire downstream permission subgraph without data loss.

Fixed-Point Computation

SharingManager.computeEffectiveRoles() repeatedly scans all edges until role assignments stabilize. This fixed-point algorithm guarantees that transitive grants—where A invites B, and B invites C—are correctly propagated and respected throughout the hierarchy.

Separation of Concerns

The permission graph is isolated within SharingManager, while the Overseer focuses on RPC handling and session management. The Overseer queries roles but delegates all UI-level actions (listing, adding, removing collaborators) to the manager. All RPC stubs follow Cap'n Web conventions, ensuring safe disposal and promise pipelining across Durable Object boundaries.

Summary

  • Cloudflare OS uses a directed permission graph in SharingManager to model collaboration, with edges representing user invitations or share-link redemptions.
  • Roles follow a strict hierarchy (use < build < owner) enforced at the API level in addCollaborator.
  • Effective roles are computed on-demand via graph traversal, determining whether a client receives full or limited overseer interfaces.
  • Lazy revocation preserves edge data for easy restoration while fixed-point computation ensures transitive permissions propagate correctly.
  • Active sessions are protected by Durable Object restarts when permissions change, and observer verification enforces third-party ACLs for non-owners.

Frequently Asked Questions

What is the highest role a collaborator can hold in a Cloudflare OS Gadget?

The highest role is owner, which grants full control over the Gadget and its permission graph. However, according to the source code in packages/workshop-backend/src/sharing.ts, a caller can only grant roles equal to or lower than their own effective role, preventing privilege escalation within the collaboration chain.

How does Cloudflare OS handle permission changes for users who currently have the Gadget open?

When a collaborator is removed or a share link is revoked, the SharingManager deletes the corresponding permission edges. If this removal affects active sessions, the workspace Durable Object is aborted, forcing all clients to reconnect. Upon reconnection, clients re-evaluate their effective roles through getEffectiveRole(), ensuring no stale permissions remain in active memory.

Can a removed collaborator be reinstated with their previous permissions intact?

Yes. Because Cloudflare OS implements lazy revocation, removing a collaborator only deletes the permission edge, not the underlying profile record or historical data. Re-adding the user restores the entire downstream permission subgraph instantly, making accidental revocations fully reversible without data loss.

How does the system verify access to third-party resources like GitHub or Google Drive?

Before a non-owner collaborator can read bound resources, the ensureObserver function verifies that the user's own third-party account has the necessary ACL (e.g., repository read access or file permissions). This observer check, documented in docs/observers.md, operates independently of the Gadget permission graph to enforce binding-aware security policies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →