# How Gadgets Are Sandboxed in Cloudflare OS: A Dual-Layer Security Model

> Discover how Cloudflare OS sandboxes Gadgets with a dual-layer security model using iframe restrictions and hardened Durable Object runtimes for secure, isolated execution.

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

---

**Cloudflare OS isolates each Gadget in a dual-layer sandbox that combines client-side iframe restrictions with a hardened server-side Durable Object runtime, ensuring code can only communicate through explicit RPC bindings and cannot access the broader internet.**

Cloudflare OS, developed in the `cloudflare/cloudflare-os` repository, uses a sophisticated **Gadget sandboxing** architecture to safely execute third-party code within the Cloudflare network. This security model employs two distinct isolation layers—client-side and server-side—that work together to prevent data leakage while enabling rich functionality through controlled capability-based communication.

## Client-Side Sandbox: Isolating Gadget UIs in Restricted Iframes

The first layer of protection begins in the browser. When a Gadget’s UI loads, the Workshop injects it into an `<iframe>` element configured with the `sandbox="allow-scripts"` attribute. This configuration, documented in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) (lines 21-24), explicitly removes capabilities that would allow the iframe to reach the outside world.

By stripping access to native browser APIs such as `fetch`, `alert`, and `confirm`, the sandbox ensures the Gadget cannot directly interact with external services or annoy users with pop-ups. The only permitted communication channel is `postMessage()`, which the Workshop wraps in a **Cap’n Web RPC channel**. This creates an RPC stub (exposed as the global `gadget` object) that points directly to the Gadget’s Durable Object on the server, establishing a type-safe, capability-based communication path.

```typescript
// Client-side RPC stub injection (packages/workshop-shared/src/api.ts)
import { RpcStub } from "capnweb";

// The Workshop injects this global `gadget` into the sandboxed iframe.
declare const gadget: RpcStub<GadgetServer>;

// Example: call a server method without direct network access
await gadget.doSomething({ foo: "bar" });

```

## Server-Side Sandbox: Restricted Workers Runtime

While the client-side iframe prevents browser-level escapes, the server-side implementation enforces strict runtime boundaries. According to [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) (lines 711-713), each Gadget executes in a **restricted variant of the Cloudflare Workers runtime** that explicitly disables arbitrary internet access.

In this hardened environment, calls to `fetch()` will fail immediately, preventing the Gadget from exfiltrating data or calling unauthorized external APIs. Instead, the code can only access external services through explicit `env` bindings defined in the deployment configuration, such as KV storage, Durable Objects, or **Gatekeeper capabilities**. This binding-only approach limits the attack surface and guarantees that the Gadget operates strictly within its granted permissions.

```typescript
// Server-side sandbox implementation (packages/workshop-backend/src/agent.ts)
export class GadgetServer implements RpcTarget {
  // `env` only contains the bindings the deployment declared.
  constructor(private readonly env: Env) {}

  async doSomething(input: { foo: string }) {
    // ❌ This will throw because fetch is disabled in the sandbox.
    // await fetch("https://example.com"); // <-- Illegal
    return { result: `Hello ${input.foo}` };
  }
}

```

## Gatekeeper UI Sandboxing

The sandboxing model extends beyond user Gadgets to include **Gatekeeper management apps**—administrative interfaces for services like Context Library, Email, and Cloudflare itself. As implemented in [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts) (lines 409-416), these configuration UIs also run inside sandboxed iframes with the same `sandbox="allow-scripts"` restriction.

Each Gatekeeper iframe receives a `ui` capability over a **MessagePort RPC session**, allowing the third-party UI to interact with the Workshop through a well-defined interface without gaining unrestricted access to the parent context. This ensures that management applications remain isolated while still capable of performing privileged operations through explicit capability delegation.

```typescript
// Gatekeeper UI capability exposure (packages/workshop-shared/src/gatekeeper.ts)
export interface GatekeeperUiFrame {
  /** HTML of the UI, served inside a sandboxed iframe. */
  html: string;
  /** Capability the UI receives to talk back to the Workshop. */
  ui: RpcStub<GatekeeperUi>;
}

```

## Runtime Enforcement Mechanisms

Cloudflare OS enforces these sandboxing policies through three complementary enforcement points that operate at different stages of the execution lifecycle:

- **Content Security Policy Headers**: The [`packages/workshop-backend/src/browser-export.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/browser-export.ts) file (lines 35-44) sets the `Content-Security-Policy: sandbox allow-scripts` header on iframe responses, ensuring browser-level sandbox enforcement regardless of iframe attributes.

- **Origin Isolation**: The build process defined in [`scripts/build-gatekeeper-configurator.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/scripts/build-gatekeeper-configurator.ts) compiles worker code without the `allow-same-origin` flag, ensuring each sandboxed realm starts with an empty principal and cannot access cookies or storage from other origins.

- **Type-Safe RPC Boundaries**: All RPC interfaces crossing the sandbox boundary are annotated with `@validateRpc()`, enforcing runtime type checking on all data exchanged between the isolated Gadget and the host system.

## Summary

- **Dual-layer isolation**: Cloudflare OS combines client-side iframe sandboxing (`sandbox="allow-scripts"`) with a server-side restricted Workers runtime to create defense-in-depth.
- **Binding-only network access**: Server-side Gadgets cannot use `fetch()` for arbitrary internet requests; they must use explicitly declared `env` bindings.
- **Capability-based communication**: All cross-boundary communication occurs through type-safe Cap’n Web RPC channels, with no direct access to browser APIs like `alert`, `confirm`, or `fetch`.
- **Universal application**: The same sandboxing rules apply to both user Gadgets and Gatekeeper management UIs, ensuring consistent security across the platform.

## Frequently Asked Questions

### What prevents a Gadget from making arbitrary network requests?

The server-side sandbox in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) explicitly disables the `fetch()` API within the restricted Workers runtime. While the client-side iframe also blocks `fetch` via the `sandbox` attribute, the server-side enforcement is absolute—any attempt to call `fetch()` throws an exception. Gadgets can only access network resources through pre-configured `env` bindings such as KV stores or Durable Objects.

### How does the client-side iframe communicate with the server?

Communication occurs exclusively through `postMessage()` wrapped in a **Cap’n Web RPC channel**. The Workshop injects a global `gadget` RPC stub into the iframe that points to the Gadget's server-side Durable Object. This stub allows type-safe method calls without exposing the underlying network transport or browser APIs.

### Are Gatekeeper management apps subject to the same sandboxing as Gadgets?

Yes. According to [`packages/workshop-shared/src/gatekeeper.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/gatekeeper.ts), Gatekeeper apps run in sandboxed iframes with identical `sandbox="allow-scripts"` restrictions. They receive a `ui` capability over a MessagePort RPC session, maintaining isolation while enabling administrative functionality through explicit capabilities.

### What happens if a Gadget tries to access browser APIs like alert or confirm?

The `sandbox="allow-scripts"` attribute on the Gadget's iframe explicitly removes access to modal dialogs and other privileged browser APIs. If the code attempts to call `window.alert()` or `window.confirm()`, the browser will throw a security error because these features are not included in the sandbox's allowlist.