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 1 of 2.

Do not use passThroughOnException for error handling

passThroughOnException() is a fail-open mechanism that sends requests to your origin when your Worker throws an unhandled exception. While it can be useful during migration from an origin server, it hides bugs and makes debugging difficult. Use explicit try...catch blocks with structured error responses instead.

Use Web Crypto for secure token generation

The Workers runtime provides the Web Crypto API for cryptographic operations. Use crypto.randomUUID() for unique identifiers and crypto.getRandomValues() for random bytes. Never use Math.random() for anything security-sensitive; it is not cryptographically secure. Node.js node:crypto is also fully supported when nodejs_compat is enabled.

Example: Secure token generation and comparison

This example shows how to use Web Crypto for secure token generation with crypto.randomUUID() and crypto.getRandomValues(), and how to compare secrets safely with crypto.subtle.timingSafeEqual(): export default { async fetch(request: Request, env: Env): Promise<Response> { const sessionId = crypto.randomUUID(); const tokenBytes = new Uint8Array(32); crypto.getRandomValues(tokenBytes); const token = Array.from(tokenBytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); return Response.json({ sessionId, token }); }, } satisfies ExportedHandler<Env>; async function verifyToken( provided: string, expected: string, ): Promise<boolean> { const encoder = new TextEncoder(); const [providedHash, expectedHash] = await Promise.all([ crypto.subtle.digest("SHA-256", encoder.encode(provided)), crypto.subtle.digest("SHA-256", encoder.encode(expected)), ]); return crypto.subtle.timingSafeEqual(providedHash, expectedHash); }

Use crypto.subtle.timingSafeEqual for secret comparison

When comparing secret values (API keys, tokens, HMAC signatures), use crypto.subtle.timingSafeEqual() to prevent timing side-channel attacks. Do not short-circuit on length mismatch. Encode both values to a fixed-size hash first to avoid leaking the length of the expected value.

API token support and recommendations

Currently, only user tokens are supported in Workers Builds, with account-owned token support coming soon. It is recommended to consistently use the same API token across all uploads and deployments of your Worker to maintain consistent access permissions.

API token permissions in Workers Builds

When creating a new API token in Workers Builds, it is automatically created with the following permissions: Account level - Account Settings (read), Workers Scripts (edit), Workers KV Storage (edit), Workers R2 Storage (edit); Zone level - Workers Routes (edit) for all zones on the account; User level - User Details (read), Memberships (read).

Deploy Hook authentication alternative

If your external system supports custom headers, you can call the manual build endpoint with an API token in the Authorization header instead of using Deploy Hooks. This gives you token-based authentication and the ability to choose the branch per request.

Deploy Hook security considerations

Deploy Hook URLs do not require a separate authorization header. Anyone with access to the URL can trigger builds for your Worker, so store them like other sensitive credentials. Store Deploy Hook URLs in environment variables or a secrets manager, never in source code or public configuration files. Restrict access to the URL to only the systems that need it. If a URL is compromised or you suspect unauthorized use, delete the Deploy Hook immediately and create a new one; the old URL stops working as soon as it is deleted.

Remove Cloudflare Workers access from GitLab account

You can remove Cloudflare Workers' access to your GitLab account by navigating to the Authorized Applications page at https://gitlab.com/-/profile/applications on GitLab, finding the application called Cloudflare Pages, and selecting the Revoke button.

GitLab app revocation affects both Workers and Pages

The GitLab application Cloudflare Workers is shared between Workers and Pages projects. Removing access to GitLab will disable new builds for both Workers and Pages, though previous deployments will continue to be hosted by Cloudflare.

GitLab organizational access for Cloudflare Workers

When you authorize Cloudflare Workers to access your GitLab account, Cloudflare Workers automatically gains access to organizations, groups, and namespaces accessed by your GitLab account. Managing access to these organizations and groups is handled by GitLab.

GitHub security best practice for scope limitation

A GitHub account should only point to one Cloudflare account. When setting up Cloudflare with GitHub for your organization, limit the scope of the application to only the repositories you intend to build with Pages. You can modify these permissions on the Applications page on GitHub (settings/installations) by selecting Switch settings context to access your GitHub organization settings, then selecting Cloudflare Workers & Pages, and for Repository access, selecting Only select repositories.

GitHub organizational access requirements

To add Cloudflare Workers installation to an organization, your user account must be an owner or have the appropriate role within the organization, such as the GitHub Apps Manager role.

Create API token with Edit Cloudflare Workers permission

To create an API token for CI/CD deployment: go to the Cloudflare dashboard Account API tokens page, select Create Token, open the Custom permission dropdown and select Edit Cloudflare Workers, customize the token name, and scope the token to specific account and zone resources.

Scope API tokens to minimum required resources

When creating an API token for CI/CD, scope it down as much as possible to limit access. For example, restrict the token to only the specific Cloudflare account where you will deploy the Worker, not all accounts you have access to.

Store CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN as CI secrets

In your CI/CD platform, add two secrets: CLOUDFLARE_ACCOUNT_ID set to your Cloudflare account ID, and CLOUDFLARE_API_TOKEN set to your generated API token. Do not store CLOUDFLARE_API_TOKEN in your repository.

Storing database credentials with Wrangler secrets

For databases requiring authentication, use Wrangler secrets to securely store credentials. Create a secret using the command 'wrangler secret put <SECRET_NAME>'. Retrieve the secret value in code using 'const secretValue = env.<SECRET_NAME>;' to authenticate with the external service.

mTLS certificate authentication for databases

For services requiring mTLS authentication, use mTLS certificates to present a client certificate for authentication. This is available through Workers mTLS bindings.

Create new MCP server instance per request to prevent cross-client leakage

Instantiate a new MCP server per request within the fetch handler to prevent response leakage between different clients.

Auth with headers example - Hono

A Workers script using the Hono framework that allows or denies requests based on a pre-shared key in a custom header. The example uses middleware applied to all routes ('*') that defines PRESHARED_AUTH_HEADER_KEY as 'X-Custom-PSK' and PRESHARED_AUTH_HEADER_VALUE as 'mypresharedkey'. It retrieves the key from the request header using c.req.header(), compares it to the expected value, and returns a 403 response with message 'Sorry, you have supplied an invalid key.' if the key does not match. If the key matches, it calls next() to proceed to the next handler. A catch-all route then passes authenticated requests to the origin using fetch(c.req.raw). ```ts import { Hono } from 'hono'; const app = new Hono(); app.use('*', async (c, next) => { const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"; const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"; const psk = c.req.header(PRESHARED_AUTH_HEADER_KEY); if (psk === PRESHARED_AUTH_HEADER_VALUE) { await next(); } else { return c.text("Sorry, you have supplied an invalid key.", 403); } }); app.all('*', async (c) => { return fetch(c.req.raw); }); export default app; ```

Auth with headers example - TypeScript

A Workers script in TypeScript that allows or denies requests based on a pre-shared key in a custom header. The example defines PRESHARED_AUTH_HEADER_KEY as 'X-Custom-PSK' and PRESHARED_AUTH_HEADER_VALUE as 'mypresharedkey'. It retrieves the key from the request header using request.headers.get(), compares it to the expected value, and returns a 403 response with message 'Sorry, you have supplied an invalid key.' if the key does not match. If the key matches, it fetches the request from the origin. The handler satisfies ExportedHandler. ```ts export default { async fetch(request): Promise<Response> { const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"; const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"; const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY); if (psk === PRESHARED_AUTH_HEADER_VALUE) { return fetch(request); } return new Response("Sorry, you have supplied an invalid key.", { status: 403, }); }, } satisfies ExportedHandler; ```

Auth with headers example - Python

A Workers script in Python that allows or denies requests based on a pre-shared key in a custom header. The example defines PRESHARED_AUTH_HEADER_KEY as 'X-Custom-PSK' and PRESHARED_AUTH_HEADER_VALUE as 'mypresharedkey'. It retrieves the key from the request header using request.headers[PRESHARED_AUTH_HEADER_KEY], compares it to the expected value, and returns a Response with status 403 and message 'Sorry, you have supplied an invalid key.' if the key does not match. If the key matches, it fetches the request from the origin. ```py from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def fetch(self, request): PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK" PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey" psk = request.headers[PRESHARED_AUTH_HEADER_KEY] if psk == PRESHARED_AUTH_HEADER_VALUE: return fetch(request) return Response("Sorry, you have supplied an invalid key.", status=403) ```

Pre-shared key authentication header names and values must be customized

The example code for header-based authentication uses generic values of 'X-Custom-PSK' for the header key and 'mypresharedkey' for the header value. To best protect resources in production, you must change both the header key and value in the Workers editor before saving the code.

Basic Authentication not suitable for production without HTTPS

HTTP Basic Authentication sends credentials unencrypted and must be used with an HTTPS connection to be considered secure. For production authentication systems, Cloudflare Access is recommended as a more secure alternative.

Store Basic Authentication password as encrypted secret

The password for Basic Authentication should be attached to the Worker as an encrypted secret and accessed via the env parameter, rather than hardcoding credentials in the code.

Logout by returning 401 without WWW-Authenticate header

To log out a user, return a 401 response without including the WWW-Authenticate header. This will invalidate the Authorization header without triggering the browser's credential prompt again.

HTTP Basic Authentication example with Node.js Buffer

This example shows how to implement HTTP Basic Authentication in a Worker using the Node.js Buffer API. It requires enabling the `nodejs_compat` compatibility flag. The example demonstrates parsing the Authorization header, base64 decoding credentials, comparing username and password using timing-safe comparison to prevent timing attacks, and returning appropriate 401/400 responses with WWW-Authenticate headers.

timingSafeEqual prevents timing attacks in credential comparison

When comparing usernames and passwords in Basic Authentication, use crypto.subtle.timingSafeEqual to prevent timing attacks. The comparison should not return early when lengths differ; instead, compare the value against itself and negate the result to avoid leaking the secret's length through timing.

Block requests by TLS version example

This JavaScript example inspects the incoming request's TLS version and blocks requests not using TLSv1.2 or TLSv1.3 by returning a 403 response: ```js export default { async fetch(request) { try { const tlsVersion = request.cf.tlsVersion; // Allow only TLS versions 1.2 and 1.3 if (tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") { return new Response("Please use TLS version 1.2 or higher.", { status: 403, }); } return fetch(request); } catch (err) { console.error( "request.cf does not exist in the previewer, only in production", ); return new Response(`Error in workers script ${err.message}`, { status: 500, }); } }, }; ```

Block on TLS version in Hono middleware

This TypeScript example using Hono framework checks TLS version in middleware and blocks requests not using TLSv1.2 or TLSv1.3: ```ts import { Hono } from "hono"; const app = new Hono(); // Middleware to check TLS version app.use("*", async (c, next) => { // Access the raw request to get the cf object with TLS info const request = c.req.raw; const tlsVersion = request.cf?.tlsVersion; // Allow only TLS versions 1.2 and 1.3 if (tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") { return c.text("Please use TLS version 1.2 or higher.", 403); } await next(); }); app.onError((err, c) => { console.error( "request.cf does not exist in the previewer, only in production", ); return c.text(`Error in workers script: ${err.message}`, 500); }); app.get("/", async (c) => { return c.text(`TLS Version: ${c.req.raw.cf.tlsVersion}`); }); export default app; ```

Block on TLS version in Python

This Python example checks TLS version and blocks requests not using TLSv1.2 or TLSv1.3: ```py from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def fetch(self, request): tls_version = request.cf.tlsVersion if tls_version not in ("TLSv1.2", "TLSv1.3"): return Response("Please use TLS version 1.2 or higher.", status=403) return fetch(request) ```

CORS proxy request rewriting pattern

When proxying requests to a third-party API from a Worker, recreate the request with the target API URL and set the Origin header to the target API's origin (scheme + netloc). This makes the API server treat the request as same-site rather than cross-site. After fetching, recreate the response object to make it mutable, then set Access-Control-Allow-Origin to the client's origin. This pattern allows a Worker to add CORS headers to APIs that don't implement CORS themselves.

CORS preflight request handling

CORS preflight requests must be identified by checking for three headers: Origin, Access-Control-Request-Method, and Access-Control-Request-Headers. If all three are present in an OPTIONS request, it is a preflight request. The response should include Access-Control-Allow-Methods (e.g., GET,HEAD,POST,OPTIONS), Access-Control-Allow-Headers (from the request's Access-Control-Request-Headers), Access-Control-Allow-Origin, and Access-Control-Max-Age (e.g., 86400 seconds). If these three headers are not all present, treat it as a standard OPTIONS request and respond with Allow header instead.

CORS response header Vary: Origin

When setting different CORS Access-Control-Allow-Origin values based on the request origin, append Vary: Origin header to the response. This instructs the browser to cache the response separately for each origin value, preventing one origin's cached response from being served to another origin.

Webhook alert on data breach detection

When sensitive data is detected in a response, the worker can post an alert to a webhook server. The alert includes the client IP address from the cf-connecting-ip request header, the current timestamp, and the full request object.

Credit card regex pattern

The regex pattern for detecting credit cards is: \b(?:4[0-9]{12}(?:[0-9]{3})?|(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b

Email regex pattern

The regex pattern for detecting email addresses is: \b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b

Text content type check for data loss prevention

The data loss prevention worker only processes responses with text MIME types (content-type containing 'text/'). If the response is not text, it passes through the origin response without scanning.

Data loss prevention with regex pattern matching

A Cloudflare Workers example demonstrates data loss prevention by using regular expressions to detect sensitive data in HTTP responses. The solution scans response text for credit card numbers, email addresses, and phone numbers. When credit card data is detected, it returns a 403 Forbidden response. When email or phone data is detected, it redacts the data by replacing it with asterisks while returning the modified response.

Phone number regex pattern

The regex pattern for detecting UK phone numbers is: \b07\d{9}\b

timingSafeEqual prevents timing attacks on string comparison

The crypto.subtle.timingSafeEqual function compares two values using a constant-time algorithm, where the time taken is independent of the contents of the values. When strings are compared using the equality operator (== or ===), the comparison ends at the first mismatched character, allowing attackers to use timing to find where the difference occurs. timingSafeEqual prevents this vulnerability.

timingSafeEqual input requirements

The timingSafeEqual function takes two ArrayBuffer or TypedArray values to compare. These buffers must be of equal length, otherwise an exception is thrown.

timingSafeEqual has timing limitations

The timingSafeEqual function is not constant time with respect to the length of the parameters and does not guarantee constant time for the surrounding code. Handling of secrets should be taken with care to not introduce timing side channels.

Comparing strings with timingSafeEqual requires TextEncoder

To compare two strings using timingSafeEqual, you must first convert them to ArrayBuffer or TypedArray using the TextEncoder API.

Never return early on length mismatch in timing-safe comparison

Do not return early when the input and secret have different lengths, as an early return leaks the length of the secret through response timing. Instead, always perform a constant-time comparison: when lengths match, compare the values directly; when lengths differ, compare the user input against itself (which is always true) and negate the result so the check still fails but takes the same amount of time.

TypeScript example: timing-safe authentication token comparison

```ts interface Environment { MY_SECRET_VALUE?: string; } export default { async fetch(req: Request, env: Environment) { if (!env.MY_SECRET_VALUE) { return new Response("Missing secret binding", { status: 500 }); } const authToken = req.headers.get("Authorization") || ""; const encoder = new TextEncoder(); const userValue = encoder.encode(authToken); const secretValue = encoder.encode(env.MY_SECRET_VALUE); // Do not return early when lengths differ — that leaks the secret's // length through timing. Instead, always perform a constant-time // comparison: when the lengths match compare directly; otherwise // compare the user input against itself (always true) and negate. const lengthsMatch = userValue.byteLength === secretValue.byteLength; const isEqual = lengthsMatch ? crypto.subtle.timingSafeEqual(userValue, secretValue) : !crypto.subtle.timingSafeEqual(userValue, userValue); if (!isEqual) { return new Response("Unauthorized", { status: 401 }); } return new Response("Welcome!"); }, }; ``` This example demonstrates timing-safe authentication by comparing an authorization header against a secret value using constant-time comparison.

Python example: timing-safe authentication token comparison

```py from workers import WorkerEntrypoint, Response from js import TextEncoder, crypto class Default(WorkerEntrypoint): async def fetch(self, request): auth_token = request.headers["Authorization"] or "" secret = self.env.MY_SECRET_VALUE if secret is None: return Response("Missing secret binding", status=500) encoder = TextEncoder.new() user_value = encoder.encode(auth_token) secret_value = encoder.encode(secret) # Do not return early when lengths differ — that leaks the secret's # length through timing. Always perform a constant-time comparison. if user_value.byteLength == secret_value.byteLength: is_equal = crypto.subtle.timingSafeEqual(user_value, secret_value) else: is_equal = not crypto.subtle.timingSafeEqual(user_value, user_value) if not is_equal: return Response("Unauthorized", status=401) return Response("Welcome!") ``` This example demonstrates timing-safe authentication in Python by comparing an authorization header against a secret value using constant-time comparison.

Hono example: timing-safe authentication middleware

```ts import { Hono } from 'hono'; interface Environment { Bindings: { MY_SECRET_VALUE?: string; } } const app = new Hono<Environment>(); // Middleware to handle authentication with timing-safe comparison app.use('*', async (c, next) => { const secret = c.env.MY_SECRET_VALUE; if (!secret) { return c.text("Missing secret binding", 500); } const authToken = c.req.header("Authorization") || ""; const encoder = new TextEncoder(); const userValue = encoder.encode(authToken); const secretValue = encoder.encode(secret); // Do not return early when lengths differ — that leaks the secret's // length through timing. Instead, always perform a constant-time // comparison: when the lengths match compare directly; otherwise // compare the user input against itself (always true) and negate. const lengthsMatch = userValue.byteLength === secretValue.byteLength; const isEqual = lengthsMatch ? crypto.subtle.timingSafeEqual(userValue, secretValue) : !crypto.subtle.timingSafeEqual(userValue, userValue); if (!isEqual) { return c.text("Unauthorized", 401); } // If we got here, the auth token is valid await next(); }); // Protected route app.get('*', (c) => { return c.text("Welcome!"); }); export default app; ``` This example demonstrates using timingSafeEqual in a Hono middleware to protect against timing attacks on authentication tokens.

Cross-Origin-Opener-Policy header

Cross-Origin-Opener-Policy should be set to "same-site; report-to=\"default\";" for security.

Cross-Origin-Resource-Policy header

Cross-Origin-Resource-Policy should be set to "same-site" for security.

Hono secureHeaders middleware example

The Hono framework provides a secureHeaders() middleware that automatically sets common security headers. Usage example: import { secureHeaders } from "hono/secure-headers"; app.use(secureHeaders());

Security headers example code

This is a working example that sets common security headers in a Cloudflare Worker. It sets X-XSS-Protection to "0", X-Frame-Options to "DENY", X-Content-Type-Options to "nosniff", Referrer-Policy to "strict-origin-when-cross-origin", Cross-Origin-Embedder-Policy to "require-corp; report-to=\"default\";", Cross-Origin-Opener-Policy to "same-site; report-to=\"default\";", and Cross-Origin-Resource-Policy to "same-site". It also deletes the headers "Public-Key-Pins", "X-Powered-By", and "X-AspNet-Version". The example checks that TLS version 1.2 or 1.3 is being used and only applies security headers to HTML responses. The code is provided in JavaScript, TypeScript, Python, Rust, and Hono variants.

X-XSS-Protection header value

X-XSS-Protection should be set to "0" to prevent a page from loading if an XSS attack is detected.

X-Frame-Options header value

X-Frame-Options should be set to "DENY" to prevent click-jacking attacks.

X-Content-Type-Options header value

X-Content-Type-Options should be set to "nosniff" to prevent MIME-sniffing attacks.

Referrer-Policy header value

Referrer-Policy should be set to "strict-origin-when-cross-origin" for security.

Content-Security-Policy configuration

Content-Security-Policy can be configured to permit content from a trusted domain and all its subdomains, such as "default-src 'self' example.com *.example.com".

Strict-Transport-Security configuration

Strict-Transport-Security can be set to "max-age=63072000; includeSubDomains; preload". These headers are not set automatically because a website might get added to Chrome's HSTS preload list.

Permissions-Policy for opting out of FLoC

Permissions-Policy header can be set to "interest-cohort=()" to allow or deny the use of browser features, such as opting out of FLoC (Federated Learning of Cohorts).

TLS version requirement for security headers

Security headers should only be applied to requests that use TLS version 1.2 or higher. Requests using older TLS versions should receive a 400 error response.

Give your agent this brain