Dynamic Worker Isolation in Cloudflare OS: Secure Gadget Sandboxing Explained

Dynamic Worker isolation is Cloudflare OS's security model that runs every user Gadget inside a network-disabled Dynamic Worker facet, restricting all external communication to explicitly provisioned capability bindings.

Every Gadget (user-created application) in Cloudflare OS executes within a strictly isolated environment. According to the cloudflare/cloudflare-os source code, this isolation leverages Cloudflare's Dynamic Workers runtime combined with Durable Object-backed workspaces to enforce sandboxing at the infrastructure level.

What Is Dynamic Worker Isolation?

Dynamic Worker isolation combines three core primitives from the Cloudflare Workers platform: Dynamic Workers, Durable Object facets, and capability-based access controls. This architecture ensures that malicious or buggy Gadgets cannot access the internet arbitrarily, leak data to other users, or tamper with the host workspace state.

The Core Architecture: Dynamic Workers and Durable Objects

Each user workspace in Cloudflare OS is itself a Durable Object. When a Gadget instantiates, the workspace creates a Dynamic Worker facet specifically for that Gadget. As implemented in packages/workshop-backend/src, this facet lives inside the same Durable Object process but maintains strict memory and execution isolation from other facets.

The OS achieves this by leveraging the HyperdriveDynamic type defined in packages/workshop-backend/worker-configuration.d.ts (lines 12904-12915). This type represents the Dynamic Worker facet and exposes lifecycle methods including dispose and get, allowing the workspace to manage isolated execution contexts programmatically.

Network Isolation and Binding Controls

Unlike standard Workers, Dynamic Worker facets run with network access disabled by default. They cannot initiate arbitrary outbound connections. Instead, the facet communicates with external services exclusively through Workers Bindings explicitly provisioned by the OS.

These bindings typically manifest as Gatekeeper interfaces—capability-based drivers that proxy specific APIs. For example, a GitHub Gatekeeper binding (GATEKEEPER_GITHUB) allows the Gadget to call GitHub APIs, but the facet cannot access any other external service or bind to unauthorized resources.

How Dynamic Worker Isolation Works Under the Hood

The implementation relies on type-safe facet management and explicit resource attachment. The OS does not implicitly trust Gadgets; instead, it constructs a minimal execution environment containing only the capabilities required for the Gadget's specific function.

The HyperdriveDynamic Type Definition

The cornerstone of this system is the HyperdriveDynamic interface. Located at packages/workshop-backend/worker-configuration.d.ts#L12904-L12915, this generated type defines the contract for creating and managing isolated facets:

// From packages/workshop-backend/worker-configuration.d.ts
interface HyperdriveDynamic {
  get(): Promise<HyperdriveDynamic>;
  dispose(): void;
  // Lifecycle and binding management methods
}

Similar definitions exist in packages/router/worker-configuration.d.ts (lines 12896-12915), indicating that both the workshop backend and router components utilize Dynamic Workers for request handling isolation.

Facet Lifecycle Management

The workspace Durable Object orchestrates the facet lifecycle through three distinct phases: creation, usage, and disposal. Each phase maintains the isolation boundary while allowing controlled interaction between the Gadget and authorized external services.

Implementing Dynamic Worker Isolation in Practice

Developers working with Cloudflare OS interact with this isolation model through explicit API calls that enforce the principle of least privilege. The following patterns demonstrate how to create, utilize, and destroy isolated Gadget facets safely.

Creating an Isolated Gadget Facet

To instantiate a Gadget with isolated network access, the workspace calls getHyperdriveDynamic with a strictly limited binding set. This example from the backend implementation shows the creation pattern:

import type { HyperdriveDynamic } from "./worker-configuration.d.ts";

export async function createGadgetFacet(
  workspace: DurableObjectState,
  env: Env,
): Promise<HyperdriveDynamic> {
  // Create facet with ONLY the specific Gatekeeper binding needed
  const facet = await workspace.getHyperdriveDynamic({
    bindings: {
      GATEKEEPER_GITHUB: env.GATEKEEPER_GITHUB,
    },
  });

  // Store reference for lifecycle management
  workspace.storage.put("myGadgetFacet", facet);
  return facet;
}

Key implementation details:

  • The bindings object contains only GATEKEEPER_GITHUB, preventing access to other services
  • The facet is created via the Durable Object API, maintaining colocation with workspace state while preserving isolation
  • The returned HyperdriveDynamic instance serves as the capability token for subsequent operations

Secure Communication via RPC Stubs

Gadget client code never communicates directly with the internet. Instead, it uses Cap'n Web RPC stubs marshaled through the parent workspace iframe. The facet enforces that only allowed Gatekeeper APIs are reachable:

// Inside the Gadget's iframe environment
import { createRpcClient } from "@gadgets/capnweb";

const rpc = createRpcClient({
  // Receives Cap'n Web stub from parent workspace
  transport: window.parent,
});

async function listRepos() {
  // Routed through facet; only github.listUserRepos is permitted
  const repos = await rpc.github.listUserRepos({ user: "me" });
  console.log(repos);
}

This architecture ensures that even if the Gadget's frontend code is compromised, it cannot bypass the facet's binding restrictions to access unauthorized APIs.

Cleanup and Resource Disposal

Proper disposal prevents resource leaks in the Workers runtime. The HyperdriveDynamic type implements the explicit resource management protocol using Symbol.dispose:

export async function deleteGadgetFacet(
  workspace: DurableObjectState,
) {
  const facet = await workspace.storage.get<HyperdriveDynamic>("myGadgetFacet");
  if (facet) {
    // Explicitly close bindings and free runtime resources
    facet[Symbol.dispose]();
  }
  await workspace.storage.delete("myGadgetFacet");
}

Alternatively, TypeScript's using declaration can manage the facet lifecycle automatically, ensuring cleanup occurs even if exceptions interrupt normal execution flow.

Capability-Based Security Model

Cloudflare OS extends Dynamic Worker isolation with a capability-based access control system. Rather than relying on ambient authority (where a process inherits all environment permissions), each facet receives only the specific capabilities required for its current task.

Least-Privilege Binding Assignment

Bindings attach to workspaces only when a specific Gatekeeper is required. As documented in the README section on capability-based access control, the facet receives the precise binding needed for that service and nothing more. This design eliminates the attack surface of over-provisioned credentials.

For instance, a Gadget processing GitHub webhooks receives only the GATEKEEPER_GITHUB binding. It cannot access databases, other APIs, or internal Cloudflare services because those bindings were never attached to its facet during creation.

Gatekeeper Integration

Gatekeepers serve as the capability-bearing interface between isolated facets and external services. Located in packages/gatekeeper-github/src/ and similar directories, these implementations validate and proxy requests, ensuring the facet's isolated context cannot be exploited to access unauthorized resources.

The combination of Dynamic Worker facets and Gatekeeper bindings creates a zero-trust execution environment where security is enforced by the runtime architecture rather than convention.

Summary

  • Dynamic Worker isolation in Cloudflare OS runs every Gadget inside a network-disabled Dynamic Worker facet within a Durable Object workspace.
  • The HyperdriveDynamic type (defined in packages/workshop-backend/worker-configuration.d.ts) provides the programmatic interface for creating and managing these isolated execution contexts.
  • Facets communicate exclusively through explicitly provisioned Workers Bindings, typically Gatekeeper interfaces that enforce least-privilege access to external APIs.
  • Network isolation prevents arbitrary outbound connections, ensuring compromised Gadgets cannot exfiltrate data or attack external services.
  • Proper resource management via Symbol.dispose or using declarations prevents memory leaks and maintains runtime efficiency in the Workers environment.

Frequently Asked Questions

How does Dynamic Worker isolation differ from standard Cloudflare Workers?

Standard Cloudflare Workers typically have full network access and share state through external storage. Dynamic Worker isolation, as implemented in Cloudflare OS, creates facets—sub-instances within a Durable Object that have network access disabled by default. These facets can only communicate through explicitly bound capabilities, whereas standard Workers operate with broader ambient authority and direct fetch capabilities.

Can a malicious Gadget escape its Dynamic Worker facet?

No. According to the cloudflare/cloudflare-os source code, facets are isolated at the runtime level within the same Durable Object process. The Dynamic Worker facet cannot observe or tamper with other facets' state, and it lacks network access to exfiltrate data. All external communication must traverse the specific Gatekeeper bindings attached during facet creation, making escape or lateral movement technically infeasible within the Workers runtime security model.

What happens if a Gadget tries to access an unbound service?

The attempt fails at the capability boundary. Because Dynamic Worker facets run with network access disabled and only possess the bindings explicitly provided in the getHyperdriveDynamic call, any code attempting to fetch unbound endpoints or access undefined environment variables simply lacks the capability to execute those operations. The Gatekeeper architecture ensures that only proxied, authorized API calls reach external services.

Where is the HyperdriveDynamic type defined in the codebase?

The primary definition resides in packages/workshop-backend/worker-configuration.d.ts at lines 12904-12915. A corresponding definition exists in packages/router/worker-configuration.d.ts (lines 12896-12915). These TypeScript declaration files are generated from the Workers runtime and export the interface used to instantiate and manage isolated Gadget facets throughout the Cloudflare OS backend.

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 →