The Agent Orchestrator runs untrusted LLM-generated code inside Cloudflare Dynamic Workers. Security is achieved through multiple layers: network isolation, capability-based bindings, credential separation, and resource limits.
Every agent Dynamic Worker has its globalOutbound set to the HttpGateway WorkerEntrypoint. This means every fetch() and connect() call from the agent is intercepted before it reaches the network.
The gateway maintains an explicit list of permitted domains. Only these can be contacted:
| Domain | Purpose |
|---|---|
api.github.com |
File reads, commits, PRs |
api.anthropic.com |
Claude LLM API |
api.openai.com |
GPT LLM API |
registry.npmjs.org |
Package metadata (for dependency agent) |
Any request to an unlisted domain receives a 403 response:
{
"error": "egress_blocked",
"message": "Outbound requests to evil.com are not permitted.",
"allowedDomains": ["api.github.com", "api.anthropic.com", "api.openai.com", "registry.npmjs.org"]
}The allowlist is configured via the ALLOWED_EGRESS_DOMAINS environment variable and can be changed without redeploying.
Every outbound request is logged with:
- Agent ID and Task ID (via
X-Agent-Id/X-Task-Idheaders) - HTTP method, hostname, and path
- Whether the request was allowed or blocked
These logs are captured by the Tail Worker and persisted to Workers Logs.
The system uses Cloudflare's Cap'n Web RPC model for capability-based security:
- Agents only receive the specific bindings they are given via
env. - There is no global registry, no service discovery. If an agent doesn't receive a binding, it cannot access that resource.
- Each binding is specialized for the specific agent via
ctx.props— a FileSystem binding for agent A points at repo A, and cannot be used to access repo B.
| Agent Type | FS (read) | FS (write) | Git | LLM | Search | Memory |
|---|---|---|---|---|---|---|
| CodeGen | Yes | Yes | Yes | Yes | Yes | Yes |
| Test | Yes | Yes | Yes | Yes | Yes | Yes |
| Review | Yes | No | Read-only | Yes | Yes | Yes |
In the current implementation, all agents receive the same binding set. Future revisions could restrict write access for review agents by defining a ReadOnlyFileSystem entrypoint.
Agents never see raw credentials. Here's how each secret is handled:
| Secret | Storage | Access Pattern |
|---|---|---|
GITHUB_PAT |
Wrangler secret | Injected by HttpGateway into outbound GitHub API requests |
ANTHROPIC_API_KEY |
Wrangler secret | Injected by HttpGateway as x-api-key header |
OPENAI_API_KEY |
Wrangler secret | Injected by HttpGateway as Authorization: Bearer header |
The HttpGateway receives credentials via ctx.props.credentials — a map of domain to credential. The gateway matches the request's hostname and injects the appropriate authentication headers.
The agent code only knows how to call env.LLM.chat(...) or env.FS.read(...). The RPC boundary ensures the agent cannot reflect on or extract the credentials from the binding stubs.
Standard Cloudflare Workers limits apply to each Dynamic Worker:
| Resource | Limit (Paid Plan) |
|---|---|
| CPU time per request | 30 seconds |
| Memory per isolate | 128 MB |
| Subrequests per request | 1,000 |
| Request body size | 100 MB |
| Response body size | No limit (streaming) |
The orchestrator can configure per-agent limits via the AgentConfig:
{
"config": {
"cpuLimitMs": 10000,
"subrequestLimit": 100
}
}Each agent runs in a separate V8 isolate:
- No shared memory between agents.
- No shared global state.
- No filesystem access (all file operations go through RPC to GitHub).
- No process spawning or system calls.
- Code is single-threaded within the isolate.
Even if an agent's LLM-generated code is malicious:
- It cannot access the network (blocked by
globalOutbound). - It cannot read secrets (credentials are in the parent Worker's scope).
- It cannot affect other agents (separate isolates).
- It cannot escape the V8 sandbox (Cloudflare's fundamental security guarantee).
Agent source is embedded as string constants in src/agents/source.ts. The orchestrator passes this source to @cloudflare/worker-bundler for compilation. The source is controlled by the system operator — agents cannot modify their own code.
If agent source includes npm dependencies (via package.json in the bundled files), they are resolved at runtime by @cloudflare/worker-bundler from the public npm registry. Consider:
- Pinning dependency versions in agent source.
- Using the
minifyoption to reduce attack surface from dependency injection. - Reviewing resolved dependency trees for critical agents.
Agents receive task descriptions from user input, which is included in LLM prompts. To mitigate prompt injection:
- System prompts are separate from user content.
- Agent output is parsed as structured data (code blocks with file paths) rather than executed as instructions.
- The self-heal loop limits retries to prevent infinite loops from adversarial prompts.
- Rotate secrets regularly — use
wrangler secret putto rotate GitHub PATs and API keys. - Restrict GitHub PAT scope — use fine-grained PATs with only the
contents:writeandpull_requests:writepermissions on specific repositories. - Enable Workers Logs — already configured in
wrangler.jsonc. Use Workers Logs to audit all agent activity. - Set
MAX_AGENT_RETRIESconservatively — defaults to 3. Lower this in production to limit runaway cost from self-heal loops. - Monitor cost — poll
GET /usageor integrate theCostTrackerDO with an alerting system. - Review before merge — the human-in-the-loop review gate (
POST /tasks/:id/review) is the last line of defense before agent code becomes a PR.