Allow lists sound simple until they become an operational process.

If people regularly need to add approved domains to Cloudflare Gateway lists, the choices are often either giving them dashboard access or asking an administrator to make every small change manually.

I built a Cloudflare Worker to provide a narrower interface: validate a domain, update the required Gateway list or lists, and maintain a backup record in Workers KV.

What I wanted to avoid

The obvious implementation would be a form that accepts a string and sends it directly to the Cloudflare API.

That creates several problems:

  • malformed domains enter the list;
  • users can target the wrong list;
  • duplicate entries become noisy;
  • API credentials risk leaking into client-side code;
  • there is no recovery record if a list is changed elsewhere;
  • every user needs broader Cloudflare permissions than the task requires.

The Worker creates a small control plane in front of the actual Gateway lists.

User

Cloudflare Access

Domain-management Worker
  ↓ validate / normalize
  ├────────→ DNS allow list
  ├────────→ HTTP allow list
  └────────→ KV backup

Put the API token behind the Worker

The browser never needs a Cloudflare API token.

The Worker holds the token as a secret and exposes only the operations the interface requires.

That is a useful least-privilege pattern even if the Worker itself needs permission to edit a list:

Human permission: use this narrow application
Worker permission: edit these Cloudflare resources

The person using the tool does not inherit the Worker’s API capability.

Normalize before changing policy

Domain input needs more care than simply checking for a dot.

I normalize and validate before making any API request.

Useful checks include:

  • trim whitespace;
  • convert to a consistent case;
  • reject schemes such as https:// when only a hostname is expected;
  • reject paths and query strings;
  • reject whitespace/control characters;
  • enforce a reasonable length;
  • validate label structure;
  • decide explicitly whether wildcard syntax is allowed.

The objective is to ensure the value written to policy is exactly the type of value the Gateway list expects.

A configuration tool should be stricter than a normal search box because its output changes a security control.

Support DNS and HTTP lists explicitly

A domain may need to be present in more than one logical list depending on how Gateway policy is structured.

The Worker supports selecting the target:

both
DNS only
HTTP only

That choice is represented deliberately in the API rather than hidden in UI behaviour.

For example:

POST /api/domain
{
  "domain": "example.com",
  "listType": "both"
}

The server decides which configured list IDs are updated.

KV is a backup, not the policy source of truth

The actual Gateway lists remain the enforcement objects.

KV stores a backup/history representation so the application can answer questions such as:

  • what domains has this tool recorded;
  • when was a value added;
  • which target list did it belong to;
  • when was the last synchronization performed.

That distinction is important.

I do not want the application to claim policy exists merely because a KV record exists. The Cloudflare list is the deployed control.

The useful relationship is:

Cloudflare list = enforcement state
KV              = recovery / visibility state

Synchronize periodically

The Worker checks whether its KV backup has been synchronized recently.

If there is no prior synchronization, it initializes the backup from the Cloudflare list. If the backup is older than a defined interval, it refreshes it again.

That allows the tool to recover from changes made outside the Worker.

Without reconciliation, KV would gradually become an inaccurate history of only the changes made through one interface.

The general pattern is the same one I use in other automation:

Read deployed state
Read stored/reference state
Reconcile deliberately
Record sync metadata

Handle partial failure

Updating multiple lists creates a partial-failure problem.

Imagine the DNS update succeeds but the HTTP update fails.

The application should not present that as a clean success.

A robust response needs to preserve the outcome for each target so the operator knows whether retry or remediation is required.

Conceptually:

{
  "dns": "updated",
  "http": "failed"
}

The backup should likewise reflect what actually happened, not what the application intended to happen.

This is one of the reasons I avoid treating a multi-step security change as a single opaque button press internally.

Protect the interface with Access

The management Worker is a natural candidate for Cloudflare Access.

Access provides the human authentication layer, while the Worker provides operation-level authorization and validation.

That gives a layered model:

Access        → who may open the tool
Worker        → what operations the tool supports
Cloudflare API → what the service token may change

Each layer has a narrower responsibility.

Add defensive API controls

Because the Worker changes security policy, I also treat its HTTP interface defensively.

Useful controls include:

  • allow-list CORS origins;
  • reject unsupported methods;
  • apply rate limiting or request throttling;
  • return structured errors without leaking secrets;
  • validate all JSON fields server-side;
  • keep secrets out of responses and logs;
  • use secure response headers on the HTML interface.

Even an internal administration tool deserves an explicit security model.

Mobile support was unexpectedly useful

A small list-management interface is a good example of an administration task that may genuinely be performed from a phone.

The UI does not need to reproduce the Cloudflare dashboard. It only needs to support the narrow workflow reliably:

  1. enter domain;
  2. choose target;
  3. submit;
  4. confirm outcome.

That makes responsive design valuable without introducing client-side complexity for its own sake.

Why I prefer this to broad dashboard access

The Worker gives me a purpose-built operational boundary.

Instead of granting somebody enough Cloudflare permissions to navigate the full Zero Trust dashboard, I can give them access to one controlled capability.

That reduces accidental change risk and makes the supported workflow obvious.

It also produces a reusable design pattern:

Put narrow, validated workflows in front of powerful infrastructure APIs rather than distributing powerful credentials to every operator.

The working project is documented in Cloudflare Allowed Domain Handler.

For a different approach to list lifecycle, see Dynamic Cloudflare Lists from Provider IPs.