| name | cloudflare-worker-security |
| description | Harden a Cloudflare Workers edge surface: authorization at the Worker boundary on every request, per-IP rate limiting, least-privilege bindings, allow-listed egress, scrubbed logs, and no secrets in code. Use when authoring or reviewing any Worker, wrangler binding, edge header, boundary auth check, or rate limiter. |
| domain | application-security |
| subdomain | cloud-edge-security |
| tags | ["cloudflare-workers","edge","authorization","rate-limiting","rls","least-privilege"] |
| nist_csf | ["PR.AC-04","PR.PT-03","PR.DS-05","DE.CM-01"] |
| owasp_asvs | ["V1","V4","V7","V13"] |
| mitre_attack | ["T1190","T1078"] |
| version | 1.0 |
| license | Apache-2.0 |
Cloudflare Worker Security
Treat every control below as load-bearing, not aspirational, for any Worker that
handles real user data or credentials — the Worker boundary is where every inbound
request is authorized and every tenant/customer query is scoped.
When to Use
Any change to a Worker: a new endpoint, a binding (R2/KV/Hyperdrive/D1/DO/Queues),
edge headers/CSP, boundary auth, rate limiting, or anything that reads a credential
or crosses an egress boundary to a third-party API. If a request can reach user data
or an external call carrying a secret, this skill is in scope.
Workflow
- Authorize at the boundary on EVERY request. Never trust the client. Admin or
internal surfaces should sit behind an access-control layer (e.g. Cloudflare
Access, a session check). Inbound webhooks/callbacks must be verified before
any processing, fail-closed — e.g. Standard Webhooks (id/timestamp/signature +
replay window) or an HMAC scheme for providers with no native signing.
Unauthenticated → reject with no side effects (no enqueue, no DB write).
- Tenant/customer isolation is a per-request concern, not the pool's job. If the
database sits behind a pooled/multiplexed connection (e.g. Hyperdrive), that pool
can bypass row-level security. Every tenant-scoped query must run inside a
transaction that sets the tenant context transaction-locally as its first
statement — never rely on a connection-level
SET, which leaks across pooled
connections. Never read tenant data outside that guarded path.
- Rate-limit public endpoints per IP. Use a KV/Durable-Object fixed-window or
token-bucket counter keyed by a hashed IP (never a raw IP), returning
Retry-After on a 429. Keep this separate from any per-tenant egress rate
limiter — a client-facing IP limiter and a backend egress token bucket solve
different problems and shouldn't share a keyspace.
- Least privilege at every binding. Each Worker should declare only the
bindings (Queues, D1/Hyperdrive, KV, R2, Durable Objects) that its own code path
actually uses — flag any unused or over-broad binding. Any downstream service
account (e.g. an ERP/API service user) should be scoped to only the
operations it needs, never a superuser or admin-scope credential.
- Allow-listed egress only. Route outbound calls to third-party APIs through an
explicit allow-list of hosts (HTTPS only). No unexpected
fetch/connect-src to
any other host. Add a new host only when that integration actually goes live —
least privilege until then.
- Scrub logs; store refs/hashes, not payloads. Never log request bodies, auth
headers, tokens, or credentials. Persist a trace id and a payload hash instead of
the raw payload where an audit trail is needed; purge PII-bearing records on a
retention schedule.