# How Cloudflare OS Manages Collaboration and Permissions for Gadgets

> Discover how Cloudflare OS manages collaboration and permissions for Gadgets using its innovative permission-graph model. Understand role computation and access controls.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: how-to-guide
- Published: 2026-09-05

---

**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`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/sharing.ts) inside the `SharingManager` class. When a client opens a workspace, the `Overseer` ([`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts)) queries this manager to determine the caller's **effective role**—the highest privilege reachable through valid permission edges.

### User Edges and Share-Link 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.

```typescript
// 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`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/sharing.ts).

### Redeeming Share Links via `redeemShareKey`

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.

```typescript
// 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).

```typescript
// 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`](https://github.com/cloudflare/cloudflare-os/blob/main/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.

```typescript
// 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`](https://github.com/cloudflare/cloudflare-os/blob/main/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`](https://github.com/cloudflare/cloudflare-os/blob/main/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`](https://github.com/cloudflare/cloudflare-os/blob/main/docs/observers.md), operates independently of the Gadget permission graph to enforce binding-aware security policies.