# How Cloudflare OS Handles Security with Gatekeepers: A Capability-Based Defense Model

> Discover how Cloudflare OS security leverages Gatekeepers and capability-based defense to isolate integrations, enforce admin controls, and handle OAuth secrets for a trusted backend.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: deep-dive
- Published: 2026-09-04

---

**Cloudflare OS isolates every external service integration in dedicated Gatekeeper workers that enforce capability-based security, admin-controlled enablement, and strict OAuth secret handling to ensure the Workshop backend never trusts raw data from third-party services.**

Cloudflare OS uses Gatekeepers to securely broker access to external APIs like GitHub or Gmail according to the cloudflare/cloudflare-os source code. Each Gatekeeper is a standalone Cloudflare Worker bound to the Workshop backend via **service bindings** (e.g., `GATEKEEPER_GITHUB`), creating a defense-in-depth model where capabilities—not raw credentials—flow across the RPC boundary.

## The Gatekeeper Security Architecture

A Gatekeeper is a standalone Cloudflare Worker dedicated to a single external service integration. The Workshop backend communicates with Gatekeepers through **service bindings** defined at deployment time. This architecture ensures that external service logic never runs inside the Workshop's privilege context; instead, the Workshop receives only **typed capability handles** that represent specific, revocable actions on remote resources.

## Three Pillars of Gatekeeper Security

### Capability-Based Access Control

The security model centers on **fine-grained capabilities**—typed RPC objects that represent exact actions a Gadget or the Workshop may perform. The root RPC interface is defined in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts), where every method returns an `RpcStub` that callers use to interact with resources.

Capabilities are **object-oriented and bounded**: each capability ties to a concrete resource URL and can be revoked by the Gatekeeper at any time. This design ensures that even if a capability handle leaks, it grants only the specific permissions it was minted for, and only on the specific resource it targets.

### Admin-Controlled Enablement

The Workshop's administrative configuration controls which Gatekeepers are available and whether they auto-provision. Admin settings live in [`packages/workshop-backend/src/admin-config.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/admin-config.ts).

Before exposing a Gatekeeper class, the system validates the request through `User.getGatekeeperClassFor()` in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts). This function checks the deployment-wide `AdminConfig` and refuses access if the Gatekeeper is disabled or if the user lacks required OAuth scopes. This prevents instantiation of disabled Gatekeepers even if a client knows the correct URL.

### OAuth and Secret Management

Gatekeepers requiring third-party OAuth obtain credentials from **deployment-time environment variables** (e.g., `GITHUB_CLIENT_ID`). The dev server in [`scripts/run-dev-server.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/scripts/run-dev-server.ts) injects these secrets into each Gatekeeper's `wrangler.dev.jsonc` configuration.

Critically, **secrets never travel over the RPC channel**. The Workshop receives only a capability handle after the OAuth flow completes within the isolated Gatekeeper worker.

## Security Enforcement Points

Cloudflare OS implements multiple enforcement layers to prevent abuse:

- **Binding Expansion Protection**: The router only routes `/gatekeeper/<slug>/*` to bindings that actually exist, preventing attackers from guessing non-existent Gatekeeper URLs. This logic resides in [`scripts/run-dev-server.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/scripts/run-dev-server.ts).
- **Admin-Level Gating**: The `User.getGatekeeperClassFor` function in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) checks `AdminConfig` before returning a Gatekeeper class, guaranteeing that disabled Gatekeepers cannot be instantiated.
- **Capability Caps**: The Workshop clamps catalog or data returned by Gatekeepers using functions like `boundAgentCatalog` in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts). This prevents malicious Gatekeepers from sending arbitrarily large payloads that could affect LLM prompts.
- **OAuth Scope Verification**: Each Gatekeeper validates its OAuth token before granting capabilities. For example, [`packages/gatekeeper-github/src/github.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-github/src/github.ts) ensures clients cannot obtain capabilities for resources they have not authorized.
- **Resource URL Pattern Checks**: The system enforces per-account restrictions through `AccountDescription.grantedResourceUrlPatterns` in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts), which lists the specific URL patterns a user may access.

## Implementing Secure Gatekeeper Access

The following patterns demonstrate how Cloudflare OS maintains security when interacting with Gatekeepers.

**Fetching a Gatekeeper class with admin verification:**

```typescript
// packages/workshop-backend/src/user.ts
const { class: GatekeeperCls, resource } =
  await account.account.getGatekeeperClassFor(url);
// AdminConfig is consulted inside getGatekeeperClassFor; if the
// Gatekeeper is disabled an error is thrown.

```

**Using capabilities with automatic resource cleanup:**

```typescript
// packages/workshop-shared/src/gatekeeper.ts
export interface Cursor<T> {
  // Must be disposed when done to avoid resource leaks.
  next(): Promise<T[] | null>;
}

// Example – reading a paginated list of Gmail messages
const cursor: Cursor<GmailMessage> = await gmailSession.listMessages();
while (true) {
  const batch = await cursor.next();
  if (!batch) break;                 // `null` signals exhaustion
  // …process batch…
}
cursor[Symbol.dispose]();            // clean up server‑side resources

```

**Bounding Gatekeeper-provided catalogs:**

```typescript
import { boundAgentCatalog } from "packages/workshop-shared/src/gatekeeper";

const rawEntries = await gatekeeper.getAgentCatalog();
const safeCatalog = boundAgentCatalog(rawEntries.entries);
// `safeCatalog` is guaranteed to respect size caps before being injected
// into the LLM prompt.

```

## Key Source Files

- [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts): Core capability-based RPC definitions and catalog bounding logic.
- [`packages/workshop-backend/src/admin-config.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/admin-config.ts): Central admin configuration for enabling or disabling Gatekeepers.
- [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts): Implements `getGatekeeperClassFor` with admin checks.
- [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts): Orchestrates session creation and capability minting.
- [`scripts/run-dev-server.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/scripts/run-dev-server.ts): Dev-time binding generation and OAuth secret injection.

## Summary

- **Capability-based isolation**: Gatekeepers expose only typed `RpcStub` objects that bind to specific resources and can be revoked at any time.
- **Administrative gating**: `User.getGatekeeperClassFor` in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) enforces `AdminConfig` checks before allowing Gatekeeper instantiation.
- **Secret isolation**: OAuth credentials reside in deployment-time environment variables injected by [`scripts/run-dev-server.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/scripts/run-dev-server.ts), never crossing the RPC boundary.
- **Defense in depth**: Multiple enforcement points—including capability caps in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts) and resource URL pattern checks—prevent payload attacks and unauthorized access.
- **Resource cleanup**: The `Cursor` pattern with `[Symbol.dispose]()` ensures server-side resources are released after capability use.

## Frequently Asked Questions

### What is a Gatekeeper in Cloudflare OS?

A Gatekeeper is a standalone Cloudflare Worker that isolates an external service integration (such as GitHub or Gmail) from the Workshop backend. Each Gatekeeper binds to the Workshop via service bindings like `GATEKEEPER_GITHUB` and exposes only fine-grained capabilities rather than raw API access.

### How does Cloudflare OS prevent unauthorized Gatekeeper instantiation?

The system uses `User.getGatekeeperClassFor()` in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) to validate requests against the `AdminConfig` defined in [`packages/workshop-backend/src/admin-config.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/admin-config.ts). If a Gatekeeper is disabled or the user lacks required OAuth scopes, the function throws an error before returning the Gatekeeper class.

### Where are OAuth secrets stored in Cloudflare OS?

OAuth secrets such as `GITHUB_CLIENT_ID` are stored as deployment-time environment variables. The dev server in [`scripts/run-dev-server.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/scripts/run-dev-server.ts) injects these into each Gatekeeper's `wrangler.dev.jsonc` configuration. Secrets remain isolated within the Gatekeeper worker and never traverse the RPC channel to the Workshop.

### How does the capability model prevent resource exhaustion attacks?

The Workshop applies **capability caps** using functions like `boundAgentCatalog()` in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts) to clamp the size of data returned by Gatekeepers. Additionally, the `Cursor<T>` interface requires explicit disposal via `[Symbol.dispose]()` to ensure server-side resources are released, preventing leaks even if the client misbehaves.