How Cloudflare OS Enforces Access Control for External Services via Gatekeepers
Cloudflare OS enforces fine-grained access control to external services through capability-based Gatekeeper adapters that implement strict RPC interfaces, with policy-driven provisioning and runtime authorization checks for every read and write operation.
Cloudflare OS manages access to third-party APIs and external resources through a sophisticated Gatekeeper architecture that treats service bindings as capabilities. This system ensures that only authorized workers can invoke external service methods, with enforcement occurring at provisioning, account creation, and runtime. Understanding how Cloudflare OS access control external services Gatekeepers function reveals a security model built on immutable capabilities and audit-ready authorization flows.
The Three-Stage Gatekeeper Enforcement Flow
The enforcement architecture operates through three distinct stages, each implemented in specific source files within the cloudflare/cloudflare-os repository.
Stage 1: Vendor Discovery and Provisioning Policy
The Workshop backend begins by enumerating all bound Gatekeeper vendors and filtering them against a deployment-wide provisioning policy. In packages/workshop-backend/src/user.ts (lines 1198‑1214), the system retrieves ambient vendors and evaluates the ambientGatekeeperMode setting, which can be enabled, optional, or disabled.
The policy also checks the shouldAutoProvisionAccount flag alongside each vendor's VendorDescription.autoProvisionsAccount property (defined in packages/workshop-shared/src/gatekeeper.ts, lines 73‑80). Only vendors explicitly marked with autoProvisionsAccount: true are considered for automatic account creation. This logic is enforced in user.ts (lines 1249‑1256), ensuring that administrators maintain strict control over which external services users may access.
Stage 2: Account Creation and Capability Sealing
When the policy permits auto-provisioning, the backend calls the optional GatekeeperVendor.createAccount() method. This method is exposed only on vendors that explicitly declare autoProvisionsAccount in their description.
The returned GatekeeperUser capability is immediately stored in the user’s Durable Object and becomes the sole authority for that external service account. As implemented in user.ts (lines 1267‑1282), this capability is immutable: the gatekeeper cannot look up or return other accounts, guaranteeing that a caller can only act on the account it just created. No OAuth flow is required at this stage, and raw credentials remain isolated within the Gatekeeper’s Durable Object, never exposed to the Workshop backend.
Stage 3: Runtime Enforcement of Reads and Writes
Every Gatekeeper session must call back into the Workshop’s authorization layer before returning data. For read operations, the system invokes ObservationAuthorizer.authorizeObservation (defined in packages/workshop-shared/src/gatekeeper.ts, lines 995‑1004), ensuring each data retrieval is audited and allowed by the user’s grant.
Write-side actions are submitted to an ApprovalQueue (lines 985‑990), which inherits the same authorization checks and may require human-in-the-loop approval via submitAction, applyAction, or rejectAction methods (lines 1015‑1024). Because the GatekeeperUser capability serves as the only entry point, the Workshop guarantees that all RPC calls route through this enforcement layer, with the service binding itself acting as a capability that only the bound worker can invoke.
Core RPC Interfaces and Capability Contracts
The Gatekeeper system relies on well-defined TypeScript interfaces that establish the security contract between the Workshop backend and external service adapters.
The GatekeeperVendor interface exposes the optional createAccount method (lines 74‑80), while the ObservationAuthorizer interface (lines 995‑1004) defines the authorizeObservation signature that Gatekeepers must call before returning sensitive data. The ApprovalQueue interface extends ObservationAuthorizer (lines 985‑990) to provide the submission and approval methods required for mutating operations.
Because each Gatekeeper runs as a separate Cloudflare Worker, the service binding itself functions as a capability. The Workshop never holds raw OAuth tokens or secrets; these remain sealed within the Gatekeeper’s Durable Object, accessible only through the capability-based RPC interface.
Implementing Gatekeeper Access Control in Practice
Listing Optional Gatekeepers for User Selection
To present users with Gatekeepers they may manually add, the backend filters for vendors where ambientGatekeeperMode === "optional":
// packages/workshop-backend/src/user.ts (conceptual usage)
const user = new UserDO(ctx);
const addable = await user.listAddableGatekeepers();
// Returns vendors where mode is "optional" and no account exists yet
// Implements logic from lines 1198-1227
This method leverages the ambient vendor discovery logic to respect deployment policy while excluding already-provisioned services.
Auto-Provisioning an Enabled Gatekeeper
When the policy allows automatic account creation, the provisioning flow executes:
// Auto-provisioning for a specific vendor
const vendorId = "context"; // A Gatekeeper marked with autoProvisionsAccount
await user.provisionAmbientAccount(vendorId);
// Internally checks ambientGatekeeperMode === "enabled" (lines 1249-1256)
// Then invokes vendor.createAccount() if defined (gatekeeper.ts lines 74-80)
This sequence corresponds to the implementation in user.ts (lines 1267‑1282), ensuring the capability is sealed immediately upon creation.
Enforcing Authorization in a Gatekeeper Session
Gatekeeper implementations must authorize observations before returning data:
// In a concrete Gatekeeper implementation
async startSession(approvalQueue) {
const authorizer = await approvalQueue; // RPC stub from Workshop
// Mandatory check before any data exposure
await authorizer.authorizeObservation({
resourceId: "user-data-123",
operation: "read",
scope: ["profile", "settings"]
});
// Only after authorization succeeds can data be returned to the Gadget
return this.fetchSecureData();
}
This pattern matches the authorizeObservation signature defined in gatekeeper.ts (lines 995‑1004), ensuring runtime enforcement of user grants.
Summary
- Policy-driven provisioning: The
ambientGatekeeperModesetting inuser.ts(lines 1198‑1227) controls whether Gatekeepers are enabled, optional, or disabled per deployment, whileautoProvisionsAccountdetermines eligibility for automatic account creation. - Immutable capabilities: The
GatekeeperUsercapability created viacreateAccount()(lines 1267‑1282) is the sole authority for an external service account, preventing privilege escalation or cross-account access. - Runtime mediation: Every read operation requires
authorizeObservationapproval (lines 995‑1004), while writes flow through theApprovalQueue(lines 985‑990), ensuring audit trails and explicit consent. - Worker isolation: Service bindings act as capabilities, keeping OAuth tokens and secrets confined to individual Gatekeeper Durable Objects rather than the Workshop backend.
Frequently Asked Questions
What is a Gatekeeper in Cloudflare OS?
A Gatekeeper is a Cloudflare Worker that acts as an adapter between the Workshop backend and external services (such as OAuth providers or APIs). It implements RPC interfaces defined in packages/workshop-shared/src/gatekeeper.ts, including optional account provisioning (createAccount) and mandatory authorization callbacks (authorizeObservation). Each Gatekeeper runs in isolation, with service bindings serving as the capability mechanism that restricts access to authorized callers only.
How does the provisioning policy determine which Gatekeepers are available?
The provisioning policy evaluates the ambientGatekeeperMode (enabled/optional/disabled) and shouldAutoProvisionAccount settings against each vendor's autoProvisionsAccount flag. In packages/workshop-backend/src/user.ts (lines 1198‑1227), the backend filters the list of bound vendors, showing only those that match the deployment policy and the user's current account state. Vendors marked as enabled are auto-provisioned, optional ones appear in addable lists, and disabled ones are hidden entirely.
What makes Gatekeeper capabilities immutable and secure?
Once created via GatekeeperVendor.createAccount() (lines 74‑80), the resulting GatekeeperUser capability is stored in the user’s Durable Object and cannot be modified or used to access other accounts. The Gatekeeper interface contract prohibits lookup operations for existing accounts, ensuring the returned capability is the only valid reference. This immutability, combined with Worker isolation that keeps secrets out of the Workshop backend, prevents lateral movement or privilege escalation between external service accounts.
How does runtime authorization prevent unauthorized data access?
Before returning any data, Gatekeepers must call ObservationAuthorizer.authorizeObservation (lines 995‑1004) with a description of the requested resource and operation. The Workshop backend evaluates this against the user’s grants and audit policies. For write operations, the ApprovalQueue interface (lines 985‑990) submits actions for review, potentially requiring human approval before applyAction executes the mutation. This design ensures that every data access—read or write—is explicitly authorized and logged.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →