new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Cloudflare Workers · all subjects

security

102 notes in this subject, read out of this brain and free to use. This is page 2 of 2.

Headers to remove for security

The headers "Public-Key-Pins", "X-Powered-By", and "X-AspNet-Version" should be deleted from responses for security purposes.

Apply security headers only to HTML responses

Security headers should only be applied to responses with Content-Type "text/html". Other content types should be returned with their original headers unchanged.

Cross-Origin-Embedder-Policy header

Cross-Origin-Embedder-Policy should be set to "require-corp; report-to=\"default\";" for security.

Sign requests with HMAC and SHA-256

Workers can generate and verify signed requests using Web Crypto APIs with HMAC and SHA-256 algorithms. For requests to /generate/ URLs, the Worker replaces /generate/ with /, signs the resulting path with a timestamp, and returns the full signed URL. For all other request URLs, the Worker verifies the signed URL using crypto.subtle.verify() and returns a 403 status if verification fails.

HMAC secret key setup for signing

To sign requests with HMAC, import a secret key using crypto.subtle.importKey() with parameters: format 'raw', algorithm name 'HMAC', hash 'SHA-256', extractable false, and usages ['sign', 'verify']. The secret should be attached to the Worker as an encrypted secret.

Use crypto.subtle.verify() to prevent timing attacks

Always use crypto.subtle.verify() to check HMAC signatures instead of signing and comparing with string comparison. String comparisons bail out on first mismatch, which leaks information to potential attackers about whether the beginning of the signature is correct.

HMAC token expiration handling

HMAC tokens should include a timestamp in the signed data and be checked for expiration. In the example, tokens expire after 60 seconds and return a 403 status with the expiry date if the current time exceeds the token's timestamp plus the expiry duration.

Signing requests example - JavaScript implementation

import { Buffer } from "node:buffer"; const encoder = new TextEncoder(); // How long an HMAC token should be valid for, in seconds const EXPIRY = 60; export default { async fetch(request, env) { const secretKeyData = encoder.encode( env.SECRET_DATA ?? "my secret symmetric key", ); const key = await crypto.subtle.importKey( "raw", secretKeyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"], ); const url = new URL(request.url); if (url.pathname.startsWith("/generate/")) { url.pathname = url.pathname.replace("/generate/", "/"); const timestamp = Math.floor(Date.now() / 1000); const dataToAuthenticate = `${url.pathname}${timestamp}`; const mac = await crypto.subtle.sign( "HMAC", key, encoder.encode(dataToAuthenticate), ); const base64Mac = Buffer.from(mac).toString("base64"); url.searchParams.set("verify", `${timestamp}-${base64Mac}`); return new Response(`${url.pathname}${url.search}`); } else { if (!url.searchParams.has("verify")) { return new Response("Missing query parameter", { status: 403 }); } const [timestamp, hmac] = url.searchParams.get("verify").split("-"); const assertedTimestamp = Number(timestamp); const dataToAuthenticate = `${url.pathname}${assertedTimestamp}`; const receivedMac = Buffer.from(hmac, "base64"); const verified = await crypto.subtle.verify( "HMAC", key, receivePreamac, encoder.encode(dataToAuthenticate), ); if (!verified) { return new Response("Invalid MAC", { status: 403 }); } if (Date.now() / 1000 > assertedTimestamp + EXPIRY) { return new Response( `URL expired at ${new Date((assertedTimestamp + EXPIRY) * 1000)}`, { status: 403 }, ); } } return fetch(new URL(url.pathname, "https://example.com"), request); }, };

Signing requests compatible with WAF HMAC validation

The signing requests example code is compatible with the is_timed_hmac_valid_v0() Rules language function. This allows verification of requests signed by the Worker script using a custom rule in the WAF.

HTMLRewriter inject CSP nonce per request

Generate a unique nonce per request using crypto.randomUUID(), inject it into both the Content-Security-Policy header and each inline <script> tag using HTMLRewriter. This enables strict-dynamic CSP policies.

HTMLRewriter inject CSP nonce example

```ts const nonce = crypto.randomUUID(); const response = new HTMLRewriter() .on("script", { element(el) { el.setAttribute("nonce", nonce); }, }) .transform(shell); response.headers.set( "Content-Security-Policy", `script-src 'nonce-${nonce}' 'strict-dynamic';`, ); return response; ```

Local dev tunnel security risk

Anyone with the tunnel URL can reach your dev server, so review what your app exposes before enabling a tunnel. Pay special attention to ungated preview or admin endpoints and review any remote bindings connected to real resources.

Local dev tunnel proxy security risk

Review any code that proxies requests to private or internal services when using a local dev tunnel, as the tunnel exposes your dev server to the public internet.

Vite dev HMR source exposure

When using the Cloudflare Vite plugin with `vite dev`, HMR and module serving may expose source files, file paths, or project structure over the tunnel. If you only need to share a built preview, prefer `vite preview` for public sharing.

Local dev routes not exposed over tunnel

Local dev-related routes such as `/cdn-cgi/*` remain restricted and are not exposed over the tunnel.

Named tunnel with Cloudflare Access

For stricter access control, use a named tunnel protected by Cloudflare Access to ensure only authorized users can reach your dev server.

Authenticate remote bindings with Cloudflare Access

If Worker is deployed behind Cloudflare Access, Wrangler must authenticate when connecting to remote bindings. Two authentication methods: (1) Interactive login via browser using 'cloudflared access login' flow (for local development with user login policy), or (2) Service token authentication for CI/CD and non-interactive environments.

Set up Cloudflare Access service token for remote bindings

To authenticate remote bindings in CI/CD: (1) Create service token in Cloudflare dashboard under Zero Trust > Access > Service Auth > Service Tokens, saving Client ID and Client Secret. (2) Add Service Auth policy to existing Access application with action 'Service Auth' and include the service token. (3) Set CLOUDFLARE_ACCESS_CLIENT_ID and CLOUDFLARE_ACCESS_CLIENT_SECRET environment variables.

Protecting temporary account API token

The account.apiToken authorizes supported resource operations and must never be exposed in browser responses or client-side code. Store it only in backend storage or server-side session storage scoped to the intended user. Exclude it from logs, analytics, and support telemetry, and delete stored copies when no longer needed and no later than the returned expiration time.

Protecting temporary account claim URL

The claim.url acts like a bearer credential; anyone with the URL can claim ownership of the temporary account. Store it only in backend storage or server-side session storage scoped to the intended user, and deliver it only to that user. Exclude it from logs, analytics, and support telemetry, and delete stored copies when no longer needed and no later than the returned expiration time.

Example GitHub webhook signature validation function

import { createHmac, timingSafeEqual } from "node:crypto"; import { Buffer } from "node:buffer"; function checkSignature(text, headers, githubSecretToken) { const hmac = createHmac("sha256", githubSecretToken); hmac.update(text); const expectedSignature = hmac.digest("hex"); const actualSignature = headers.get("x-hub-signature-256"); const trusted = Buffer.from(`sha256=${expectedSignature}`, "ascii"); const untrusted = Buffer.from(actualSignature, "ascii"); return ( trusted.byteLength == untrusted.byteLength && timingSafeEqual(trusted, untrusted) ); } This function validates that the GitHub webhook payload has not been tampered with.

Airtable Personal Access Token setup for form handler

When creating a Personal Access Token in Airtable for use with Workers, configure it with the 'data.records:write' scope to allow creating new records. Grant access only to the specific Airtable base where form submissions will be stored. Store the token as a wrangler secret named AIRTABLE_ACCESS_TOKEN.

Store Resend API key as a secret

Resend API keys should be stored as secrets in Cloudflare Workers. For local development, create a .dev.vars file with RESEND_API_KEY=your_key. For deployed workers, use npx wrangler secret put RESEND_API_KEY to add the secret. Access the secret via the env parameter in the fetch handler as env.RESEND_API_KEY.

Secure R2 uploads with Bearer token authentication

To secure R2 uploads, use Wrangler secrets to store an authentication token. Retrieve the Authorization header from the request and compare it against the expected format `Bearer ${env.AUTH_SECRET}`. Return a 401 Unauthorized response if the header is missing or does not match. Store the secret using `npx wrangler secret put AUTH_SECRET`.

Preview URLs access control with Cloudflare Access

When enabled, all preview URLs are publicly available. You can use Cloudflare Access to require authentication before accessing preview URLs. Limit access to yourself, teammates, your organization, or specific email addresses by configuring an access policy. Enable Cloudflare Access in the Cloudflare dashboard under Workers & Pages > your Worker > Settings > Domains & Routes > Preview URLs > Enable Cloudflare Access.

Secrets overview and deployment methods

Secrets are used for storing sensitive information such as API keys and auth tokens. For deployed Workers, secrets are set via the dashboard or Wrangler CLI.

Date.now() returns time of last I/O, not current time

Date.now() in Workers returns the time of the last I/O event, not the current time. It does not advance during code execution. This prevents attackers from measuring code execution time locally using timers, which is essential for Spectre attacks.

No multi-threading or shared memory in Workers

Workers does not permit multi-threading or shared memory. All processing related to one event happens on the same thread. Multiple Workers cannot operate on the same request concurrently. This prevents attackers from racing threads to construct ad hoc timers for timing attacks.

Workers moved to separate process when using debugger

When a developer uses the devtools debugger to inspect their Worker, Cloudflare runs that Worker in a separate process. This is because the inspector protocol has not received as much security scrutiny as the rest of V8, so process-level sandboxing provides extra protection against inspector protocol bugs.

Layer 2 sandbox uses Linux namespaces and seccomp

Layer 2 sandbox uses Linux namespaces and seccomp to prohibit all access to the filesystem and network. Namespaces are configured after the process starts but before any isolates load, allowing Cloudflare to use a totally empty filesystem and block all filesystem-related system calls. Network access is blocked entirely; the process can only communicate over local UNIX domain sockets.

Workers patch gap is under 24 hours

The time between V8 publishing a security patch and Cloudflare deploying it to production (the patch gap) is under 24 hours. Cloudflare has automated nearly the entire build and release process so that when a V8 patch is published, systems automatically build a new Workers runtime release and deploy it to production after human review.

Only JavaScript and WebAssembly allowed, no native code

Workers does not allow customers to upload native-code binaries. Only JavaScript and WebAssembly are permitted. Both are passed through V8 to convert into native code. This prevents attackers from using instructions like CLFLUSH that are useful for side channel attacks.

Outbound HTTP requests routed through local proxy service

All outbound HTTP requests from Workers are sent over a UNIX domain socket to a local proxy service. The proxy verifies that requests are addressed to public Internet services or the Worker's zone origin server, not to internal services. It adds a header identifying the Worker origin to every request for tracing and blocking of abusive requests.

Inbound requests processed by inbound proxy service

Inbound HTTP requests do not go directly to the Workers runtime. They are received by an inbound proxy service that performs TLS termination (Workers runtime never sees TLS keys) and identifies the correct Worker script for the request URL. Once validated, the request is passed to the sandbox process over a UNIX domain socket.

Dynamic process isolation for suspicious Workers

The runtime can reschedule any Worker with suspicious performance metrics into its own process. Spectre attacks exhibit abnormal behavior shown in CPU performance counters. If a Worker shows suspicious patterns or uses significant CPU time per event, it is isolated in its own process, allowing reliance on OS Spectre defenses.

Date.now() timing measure implemented before Spectre announcement

The measure of returning Date.now() as the time of last I/O rather than current time was implemented in mid-2017, before Spectre was announced. This was implemented because Cloudflare was already concerned about side channel timing attacks and designed the system with side channels in mind from the start.

Remote timing attacks difficult but not impossible

While local timing measurement is prevented, remote timing attacks through HTTP client response measurement remain theoretically possible. Such measurements are noisy due to internet traversal and networking costs. Multiple attack runs and averaging could overcome noise. However, Cloudflare has not been able to develop a remote timing attack that works in production with adversarial testing.

Periodic whole-memory shuffling future defense

Cloudflare plans periodic restarting of the entire Workers runtime on a daily basis to reset memory locations and force attacks to restart discovering secret locations. Workers can also be rescheduled across physical machines or cordons to limit the window to attack any particular neighbor.

Cloudflare collaborating with TU Graz on Spectre research

Cloudflare is working with researchers at Graz University of Technology (TU Graz) to study security defenses against Spectre. These researchers include some of the original Spectre discoverers. Research results will be published as they become available.

Workers designed resistant to Spectre-style attacks

Workers runtime is resistant to the entire range of Spectre-style attacks without requiring special attention to each one and without needing to block speculation in general. The approach is different from the industry standard and requires careful study.

No filesystem API means no filesystem access

Currently, Workers does not allow any access to the local filesystem. Therefore, Cloudflare does not expose a filesystem API at all. The principle is that no API means no access.

Workers limited to HTTP for network access

Today, Workers are only allowed to communicate with the rest of the world via HTTP, both incoming and outgoing. There is no API for other forms of network access, so they are prohibited. Cloudflare plans to support other protocols in the future.

Give your agent this brain