# How Cloudflare OS Implements Capability-Based Security with Gatekeepers

> Discover how Cloudflare OS uses Gatekeepers and capability-based security to isolate services and enforce least-privilege access with URL-granular permissions and Durable Object accounts.

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

---

**Cloudflare OS isolates external services behind lightweight Gatekeeper Workers that expose only typed RPC stubs to applications, enforcing least-privilege boundaries through URL-granular permissions and Durable Object-backed accounts.**

Cloudflare OS (codenamed "Workshop") implements a rigorous capability-based security model using Gatekeepers to isolate external integrations. In the `cloudflare/cloudflare-os` repository, each gatekeeper encapsulates sensitive credentials within Durable Objects and exposes only minimal, typed capabilities to agent workspaces. This architecture ensures that applications receive exactly the access they need—nothing more—through a combination of provisioned accounts, strict resource URL validation, and ambient capability injection.

## Gatekeeper Architecture: Three Layers of Enforcement

Cloudflare OS enforces capability boundaries at three distinct architectural layers. Each layer acts as a checkpoint to prevent unauthorized access to external service credentials.

### The Binding Layer

Service bindings determine which capabilities are ambient to a workspace. In [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts), the system creates binding objects through `GatekeeperLoopback` and `GatekeeperHookLoopback` classes. These bindings register the gatekeeper's `suggestedBindingName` as a named chat binding during the `prepareChatBindings` startup sequence.

### The Account Layer

A gatekeeper-provided `GatekeeperUser` instance owns the actual capability (such as an OAuth token or read-only API key). Each gatekeeper implements Vendor and User classes—for example, `GatekeeperVendor` and `GatekeeperUserImpl` in [`packages/gatekeeper-context/src/library-gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-context/src/library-gatekeeper.ts). The account's sensitive state persists in a Durable Object, ensuring secrets never leave the gatekeeper's isolation boundary.

### The Resource-URL Granularity Layer

The `getGatekeeperClassFor(url)` method maps URLs to concrete capability classes, ensuring agents can only reach declared resources. Central logic lives in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts) (lines 590-620), consumed by the backend at [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) (line 1666) to resolve resource URLs against the gatekeeper's whitelist.

## The Capability Flow: From Provisioning to Enforcement

Cloudflare OS manages capabilities through a four-stage lifecycle that keeps credentials encapsulated while making functionality available to agents.

### 1. Account Provisioning

When a gatekeeper is **auto-provisioned**, its `GatekeeperVendor.createAccount()` method instantiates a new `GatekeeperUser` without requiring user identity. The capability (such as an OAuth token) stores within a Durable Object (`GatekeeperUserImpl`). This pattern appears in [`packages/gatekeeper-context/src/library-gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-context/src/library-gatekeeper.ts):

```typescript
export class GatekeeperVendor extends WorkerEntrypoint<Cloudflare.Env, GatekeeperVendorProps> {
  // auto-provisions an account that provides a read-only session
  async createAccount() { 
    // Creates GatekeeperUserImpl instance with embedded credentials
  }
}

```

### 2. Binding Registration

The workshop's `AdminConfig` enumerates enabled gatekeepers. During startup, `prepareChatBindings` in [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts) registers each gatekeeper's binding name as a *named chat binding*, making the capability ambient to eligible workspaces.

### 3. Capability Injection

When an agent executes code via `executeCode`, the system calls `getSession` or `getAgentCatalog` RPC methods on the gatekeeper's `GatekeeperUser`. These return a **stub** that the agent embeds in its workspace. Because the stub represents a capability rather than a credential, it can traverse RPC boundaries without exposing raw secrets.

```typescript
// The singleton stub exposed to agents
export class GatekeeperUserImpl extends WorkerEntrypoint<Cloudflare.Env, GatekeeperUserImplProps> {
  // Returns a read-only session for the agent
  async getSession() { 
    // Returns capability stub, not raw token
  }
}

```

### 4. Runtime Enforcement

All external service calls must route through the gatekeeper's RPC interface. The `user.ts:getGatekeeperClassFor` method validates URLs against the gatekeeper's whitelist, throwing if the resource is not explicitly allowed.

## Security Boundaries and Provisioning Policies

Cloudflare OS implements multiple mechanisms to prevent accidental capability escalation.

### Ambient vs. Explicit Capabilities

Only gatekeepers marked as *auto-provisioned* become ambient capabilities automatically. All others require explicit addition through the admin UI. This distinction prevents privileged capabilities from inadvertently attaching to workspaces.

### Three-State Provisioning Mode

The [`provisioning-policy.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/provisioning-policy.ts) module defines three states: `enabled`, `optional`, and `disabled`.

- **Enabled**: The gatekeeper auto-creates an account for every user, guaranteeing capability availability while hiding it from the UI
- **Optional**: Users must opt-in via the Connectors UI
- **Disabled**: No capability is created or available

### Resource-URL Grammar

The [`resources.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/resources.ts) module defines a strict grammar for capability URLs. When the backend calls `getGatekeeperClassFor`, it parses the URL and returns a typed class. For example, a **private** collection returns a capability class restricted to the owning account, while a **public** collection returns a shared capability class:

```typescript
// packages/workshop-backend/src/user.ts
const { class: cls, resource } = await account.account.getGatekeeperClassFor(url);

```

## Backend Enforcement with getGatekeeperClassFor

The core enforcement mechanism resides in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts). This utility classifies URLs and returns the appropriate capability constructor. The implementation (around lines 590-620) applies the resource grammar rules to ensure that a capability for `context://public/docs` cannot access `context://private/secrets`.

When a workspace requests the Context capability, the resolution flow works as follows:

```typescript
// Front-end: request the Context capability
const ctx = await rpc.getGatekeeperClassFor('context://my-collection');
const session = await ctx.getSession();   // Returns a read-only stub
const docs = await session.listDocuments(); // RPC call uses the capability internally

```

The backend validates the gatekeeper's provisioning policy before returning the capability:

```typescript
// Backend: enforce capability when a user attempts to add a new gatekeeper
import { AdminConfig } from 'packages/workshop-backend/src/admin-config';
if (gatekeeper.autoProvisionsAccount && AdminConfig.provisioningPolicy === 'disabled') {
  throw new Error('Auto-provisioned gatekeepers cannot be disabled');
}

```

## Summary

- **Capability Isolation**: Each external service runs in its own Gatekeeper Worker with Durable Object-backed accounts, ensuring secrets never leak to agent workspaces.
- **Typed RPC Stubs**: Gatekeepers expose only `getSession` or `getAgentCatalog` RPC methods that return capability stubs, not raw credentials.
- **Deterministic Binding**: The `suggestedBindingName` creates a predictable naming scheme for capability injection via `GatekeeperLoopback` in [`overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/overseer.ts).
- **URL-Granular Enforcement**: The `getGatekeeperClassFor` method in [`gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/gatekeeper.ts) (lines 590-620) and [`user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/user.ts) (line 1666) validates resource URLs against strict grammars to prevent capability misuse.
- **Provisioning Controls**: Three-state provisioning policies (`enabled` | `optional` | `disabled`) allow administrators to control whether capabilities are ambient, opt-in, or unavailable.

## Frequently Asked Questions

### What is a Gatekeeper in Cloudflare OS?

A Gatekeeper is a lightweight Cloudflare Worker that encapsulates an external service integration. It owns a Durable Object account (`GatekeeperUserImpl`) that stores credentials such as OAuth tokens or API keys. Rather than exposing these secrets directly, the Gatekeeper exposes typed RPC methods like `getSession()` that return capability stubs, allowing agents to interact with external services without ever handling raw authentication material.

### How does Cloudflare OS prevent capability leakage between workspaces?

The system enforces isolation at the binding and account layers. In [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts), the `GatekeeperLoopback` class creates service bindings that are only injected into workspaces where the administrator has explicitly enabled them. Additionally, the `getGatekeeperClassFor` method in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) validates that the requesting workspace's account owns the requested resource URL, throwing an error if a workspace attempts to access another's private capabilities.

### What is the difference between auto-provisioned and optional gatekeepers?

According to [`packages/workshop-shared/src/provisioning-policy.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/provisioning-policy.ts), auto-provisioned gatekeepers automatically create a `GatekeeperUser` account for every user when set to `enabled` mode, making the capability ambient and hidden from the UI. Optional gatekeepers require users to explicitly connect the service through the Connectors UI. Disabled gatekeepers cannot be instantiated or used by any workspace, providing a hard kill-switch for vulnerable integrations.

### How does resource URL granularity enforce security boundaries?

The [`resources.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/resources.ts) module and `getGatekeeperClassFor` implementation parse resource URLs to determine capability scope. For example, a URL like `context://private/user-123/data` returns a capability class restricted to that specific user, while `context://public/shared` returns a shared capability class. This grammar ensures that a capability granted for public resources cannot be reused to access private resources, even if both are served by the same Gatekeeper backend.