Move outbound rules·续
If your class uses DirectoryBackup from Move backups, call this.backups.intercept() in the try block, before your intercept(). Otherwise every backup and restore fails with BACKUP_TRANSFER, because your Outbound entrypoint receives backup requests from the container. Call it only in startContainer(), not on every request.
The HTTPS intercept signs responses with a certificate that the container does not trust by default. As in 0.12, the TRUST command adds the certificate to the system bundle, which curl and Git read. ENV sets NODE_EXTRA_CA_CERTS and REQUESTS_CA_BUNDLE for Node.js and Python requests, which do not read the bundle by default. Keep your other ENV values, and pass ENV to each exec() call. The image needs the ca-certificates package, which provides the bundle.
The certificate appears once the HTTPS intercept is registered. TRUST waits up to 10 seconds for it. 0.12 waited up to 5 seconds. If the certificate does not appear, or the append fails, trust() throws, and startContainer() stops the container. Each container gets a new certificate. A snapshot saves the bundle with the certificate of the container it came from, so appending to the bundle would keep certificates from earlier containers. TRUST therefore keeps a copy of the system bundle as it was before any certificate was added, and writes the bundle again from that copy and the current certificate. Running it again for the same container writes the same bundle.
Replace the runtime setters
Add methods that update the stored rules, and register the entrypoint again with them:
src/index.tsts
export class MySandbox extends DurableObject<Env> {
// ...
private async updateRules(change: StoredRules): Promise<void> {
this.ctx.storage.kv.put(RULES, { ...this.rules(), ...change });
if (this.container.running) {
await this.intercept();
}
}
async allowHost(hostname: string): Promise<void> {
const hosts = this.rules().allowedHosts ?? ALLOWED_HOSTS ?? [];
await this.updateRules({ allowedHosts: [...hosts, hostname] });
}
async setOutboundByHost(
hostname: string,
method: string,
params?: unknown,
): Promise<void> {
const hosts = { ...this.rules().outboundByHostOverrides };
hosts[hostname] = { method, params };
await this.updateRules({ outboundByHostOverrides: hosts });
}
}
Registering the entrypoint again replaces the rules of a running container, and open connections pick up the new rules without being dropped. Write the other setters the same way. Each one passes a new value to updateRules():
| 0.12 | Value to pass |
|---|---|
setAllowedHosts(hosts), setDeniedHosts(hosts) |
allowedHosts or deniedHosts, set to hosts |
denyHost(), removeAllowedHost(), removeDeniedHost() |
deniedHosts or allowedHosts, with the hostname added or removed |
setOutboundByHosts(), removeOutboundByHost() |
outboundByHostOverrides, with the hostnames added or removed |
setOutboundHandler(method, params) |
outboundHandlerOverride, set to { method, params } |
Like 0.12, each list that a sandbox sets replaces the class list for that sandbox.
What changes for requests
0.12 checked HTTPS requests only when your class set interceptHttps = true. Without it, HTTPS requests to allowed hostnames failed when enableInternet was false, and HTTPS requests to denied hostnames succeeded when it was true. The entrypoint checks every HTTPS request, so commands that relied on either behavior now follow your rules.
In 0.12, handlers that your class declared as static fields, such as static outboundByHost = { … }, never ran when TypeScript compiled class fields as definitions. That is the default for target ES2022 and later. The field skipped the setter that registers the handlers, so requests to those hostnames went to the Internet or got 520. In 1.0, the handlers run.
HTTPS requests to an IP address, such as https://203.0.113.10/, fail while * is intercepted and enableInternet is false. Use a hostname.
Replace gitCheckout()
In 0.12, gitCheckout() clones a repository into /workspace:
src/index.ts (0.12)ts
const repo = "https://github.com/octocat/Hello-World";
await sandbox.gitCheckout(repo, { depth: 1 });
In 1.0, run git clone in a Durable Object method:
src/index.ts (1.0)ts
export class MySandbox extends DurableObject<Env> {
// ...
async clone(url: string, dir: string): Promise<void> {
await this.ensureRunning();
const git = ["git", "clone", "--filter=blob:none", "--depth", "1"];
const clone = await this.container.exec(
["timeout", "-k", "5", "600", ...git, "--", url, dir],
{ cwd: "/workspace", env: ENV },
);
const output = await clone.output();
if (output.exitCode !== 0) {
throw new Error(new TextDecoder().decode(output.stderr));
}
}
}
0.12 cloned with --filter=blob:none into /workspace/<repository name>, and stopped the clone after 600 seconds. Pass --branch for the branch option. To read the branch that 0.12 returned, run git branch --show-current with cwd set to the clone. The image needs Git, and your rules must allow the hostname of the Git server, such as github.com. To clone a private repository without giving the sandbox a token, refer to Clone a private repository.
Check the rules
After you deploy the switch, run these commands in a sandbox whose image has curl and Git. A hostname that your rules block returns the 0.12 response:
curl -s -w ' %{http_code}\n' https://example.com/
Origin is disallowed 520
An allowed hostname works over HTTPS without a certificate option on the command:
git ls-remote https://github.com/octocat/Hello-World HEAD
7fd1a60b01f91b314f59955a4e4d4e80d8edf11d HEAD
In a sandbox that called allowHost() in 0.12, requests to that hostname still succeed after the switch.
Related resources
- Durable Object container API
- Handle outbound traffic
- Call an authenticated API from a sandbox
- Clone a private repository
- Move bucket mounts
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/migrate/outbound-traffic/#page","headline":"Move outbound rules","description":"Replace @cloudflare/sandbox 0.12 outbound rules with a WorkerEntrypoint that your Durable Object registers, and replace gitCheckout() with git clone.","url":"https://developers.cloudflare.com/sandbox/sdk/migrate/outbound-traffic/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/migrate/outbound-traffic/og.png?v=a5df19060d257d2f","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Choose how to switch live Sandbox SDK 0.12 sandboxes to 1.0, prepare your code for the deploy that cannot be undone, and remove what 0.12 leaves behind. title: Plan the move to Sandbox SDK 1.0 image: https://developers.cloudflare.com/sandbox/sdk/migrate/plan-the-move/og.png?v=3758c9f5660c5104
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs