Security in Cloudflare OS is built into every layer of the architecture — not bolted on afterward. From the sandbox that isolates each Gadget’s code, to the capability model that requires explicit introductions before any external resource can be accessed, to the observer tracking system that prevents data leaks when you share a Gadget with a teammate, the system is designed so that non-technical users can “go nuts” and nothing bad will happen.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/cloudflare/cloudflare-os/llms.txt
Use this file to discover all available pages before exploring further.
Gadget sandboxing
Every Gadget runs in a two-layer sandbox: one for the server and one for the client.Server sandbox
The server-side code runs in a Dynamic Worker Facet — a Cloudflare Worker spun up on demand and scoped to this Gadget. Internet access is disabled at the runtime level: the Gadget’s server cannot make arbitrary outbound HTTP requests. It can only communicate with the external world through explicitly configured Workers Bindings — service bindings to Gatekeeper Workers that the user has consciously set up.The underlying runtime,
workerd, is open source on GitHub and fully auditable. The sandbox is not a policy layer on top of a general-purpose runtime — it is enforced by the runtime itself.Client sandbox
The client-side code runs in a sandboxed<iframe> with both Content-Security-Policy restrictions and the sandbox attribute applied. The iframe cannot reach the network directly. Its only communication channel with the outside world is a Cap’n Web RPC session provided to it over postMessage() from the parent Workshop frame.
This design means a security bug in a user’s custom Gadget UI code cannot leak data to an attacker on the internet — the browser itself enforces the isolation.
Client–server communication
The only path between the Gadget’s client and server is Cap’n Web RPC overpostMessage. This is not just a security constraint — it also means every Gadget automatically exposes a structured, typed API that AI agents can call directly using Code Mode, with no extra integration work required.
Capability-based access control
By default, every agent and every Gadget has access to nothing. Even if your deployment has been configured with Gatekeeper integrations for Google, GitHub, Linear, and others, agents and Gadgets do not get access to any of them automatically.Explicit introductions
Access is granted by introducing an agent or Gadget to a specific resource — pasting a URL, selecting a document from a picker, or approving an agent’s request for a connection. Each introduction creates a scoped Gatekeeper binding covering exactly that resource, not the user’s entire account on the service. This is a deliberate departure from how most agent harnesses work, where MCP servers are configured once and their full capabilities are ambiently available in every chat session. In Cloudflare OS, each agent is restricted to only the access it actually needs for the job at hand.No ambient authority by default
A Gatekeeper must never assert its own ambience. Resources become ambient (auto-injected into every agent context) only through explicit admin configuration — the three-state auto-provisioning mode (disabled / optional / enabled) in the Admin Panel. See the Admin Panel guide for details.
disabled
The Gatekeeper is offered to no one, including existing account holders.
optional
Users can opt in from the Connectors page. Nothing is auto-provisioned.
enabled
An account is automatically provisioned for every user and silently folded into their agent context.
Human-in-the-loop approvals
All write actions performed by an agent or Gadget are routed through the Gatekeeper approval queue before they take effect. This is not a synchronous gate that blocks the agent — it is designed so that approvals can happen asynchronously, after the agent has finished its work.How asynchronous approval works
Agent submits an action
When an agent (or Gadget code) calls a write method on a Gatekeeper, the Gatekeeper submits an action description to the approval queue via
submitAction() and returns immediately.Gatekeeper simulates the result
The Gatekeeper updates its local cache or simulation state to reflect what the world would look like if the action had been applied. Subsequent reads by the agent return the simulated state.
Agent continues unblocked
The agent proceeds with follow-on work, potentially queuing up more dependent actions, all against the simulated state.
User reviews in bulk
When the agent finishes, the user can review the queued actions in the Workshop UI and approve or reject them in bulk — at a time that is convenient for them.
Observer tracking
When you share a Gadget that uses Gatekeeper bindings, the sharing system must ensure that your collaborators cannot use the Gadget as a back channel to access data they couldn’t access themselves. This is enforced through observer tracking.The problem observer tracking solves
Without observer tracking, sharing a Gadget that has read from your private Google Doc would effectively grant a collaborator indirect read access to that document — even if they don’t have permission on Google’s side. The earlier approach was a bluntprohibitAllSharing flag: any Gatekeeper that handled sensitive data could mark itself as non-shareable, which locked down the entire Gadget for everyone. Observer tracking replaces that with a per-user, per-Gatekeeper check.
How observer verification works
Collaborator opens the Gadget
When Bob opens a Gadget shared by Alice, the Overseer determines that Bob is a non-owner collaborator and checks whether he has been verified as an observer for each of the Gadget’s Gatekeeper bindings.
Bob provides his connected accounts
For each Gatekeeper binding in scope (all bindings for
build collaborators; named bindings only for use collaborators), Bob must have his own connected account for that vendor. If he doesn’t, he is prompted to connect or select one.Bob's account mints a verifier
Bob’s connected account calls
GatekeeperUser.getVerifier(), which returns an opaque GatekeeperUserVerifier service stub. This verifier represents Bob’s vendor-level identity without revealing it to the Overseer.The Gatekeeper checks access
The Overseer passes the verifier to
Gatekeeper.addObserver(observerId, verifier). The Gatekeeper — the authority on its own resource’s ACL — unwraps the verifier (via a non-standard method it defined on its own verifier class) and checks whether Bob’s account can access everything the Gadget has read so far. If not, addObserver() throws and Bob is denied.Observer IDs and forward exclusion
The observer ID is opaque by design: it is a randomly generated string with no connection to a user’s email or profile ID. Gatekeepers that want to block a specific future observation from reaching a specific observer can do so by listing that observer’s ID inObservationDescription.excludeObservers. The Overseer will block the observation unless every named observer has already lost access in the sharing graph.
Comparison with prohibitAllSharing
| Mechanism | Behavior |
|---|---|
prohibitAllSharing: true | Blunt all-or-nothing: the Gadget cannot be shared with anyone once this flag is set. |
| Observer tracking | Per-user, per-Gatekeeper: collaborators who can independently access the underlying data are admitted; others are blocked at verification time. |
What happens when a collaborator loses access
When a collaborator is removed from the sharing graph, the Overseer also tears down their observer record and callsremoveObserver(observerId) on all relevant Gatekeeper facets. This is idempotent — Gatekeepers are required to handle unknown IDs gracefully. The Overseer also aborts the active session, forcing a reconnect that re-evaluates the now-changed permission graph. See the Sharing and Collaboration guide for details on live session termination.
Admin security boundaries
The Admin Panel allows deployment customization through theAdminConfig object — site branding, agent instructions, Gatekeeper provisioning modes, and so on. However, certain settings are deliberately kept out of the Admin Panel and remain env-var driven.
The AdminSettings Durable Object owns the authoritative AdminConfig and mirrors it to a single KV key for fast hot-path reads. The AdminApi capability is only minted for users listed in the ADMINS env var — non-admins receive null from getAdminApi() and cannot call any admin methods.