seekrit
← all posts

One token for your Cloudflare Python Worker's secrets

Integrations

Cloudflare made Python Workers generally available on September 21, 2026. A Python API can now run on Workers, use Cloudflare bindings, and keep the frameworks its team already knows. The first external API call still brings a familiar problem: where should its credential live?

For a Worker, the usual answer is a secret binding. Add a GitHub token with wrangler secret put GITHUB_TOKEN, read self.env.GITHUB_TOKEN, and repeat for each provider, Worker, and environment. That works. It also makes every deployment's configuration another place to maintain the same credentials.

Seekrit's Python SDK now has an async Worker adapter. Store one service token in Cloudflare, resolve the application's environment at the point of use, and decrypt the response inside the Worker. The setup stays one binding as the application adds more credentials.

The runtime change that matters

The regular Python SDK was written for a process: it calls urllib, decrypts with cryptography, and can load the result into os.environ. Its async HTTP transport adapters run that synchronous resolve in a thread executor.

A Python Worker runs in Pyodide. Cloudflare exposes a native async fetch through the workers module, and its threading module is not functional. The integration belongs on that async path. It also needs packages compiled for the WebAssembly runtime rather than a developer's laptop.

The new seekrit.cloudflare.AsyncClient uses workers.fetch, with a timeout that covers the response body and an abort signal for cancellation. It refuses redirects. The crypto implementation stays the same: Pywrangler installs the Pyodide build of cryptography, and the client unwraps data keys, decrypts each layer, merges the app over its groups, and expands secret references.

That distinction is backed by tests: the adapter is exercised against the SDK's shared decryption fixture, including Unicode, empty values, layer precedence, and references. Local Worker checks cover the runtime and its actual package bundle. Those checks do not constitute a production deployment or a live Seekrit authentication test.

One binding, resolved in the handler

Create a service token for the application's Seekrit environment, with key grants for its app and composed groups. Store it through the interactive Cloudflare secret prompt:

uv run pywrangler secret put SEEKRIT_TOKEN

Then resolve inside the handler:

from workers import Response, WorkerEntrypoint

from seekrit import SeekritError
from seekrit.cloudflare import AsyncClient


class Default(WorkerEntrypoint):
    async def fetch(self, request):
        try:
            secrets = await AsyncClient(
                token=getattr(self.env, "SEEKRIT_TOKEN", None),
                timeout=10.0,
            ).resolve()
        except SeekritError:
            return Response("Secret resolution unavailable", status=503)

        # Use the mapping in application code; never return its values.
        return Response.from_json({"configured": bool(secrets)})

The response is deliberately just a setup check. In an application, pass secrets["GITHUB_TOKEN"] directly to the GitHub client, or use it in the Authorization header of a request to a fixed, trusted GitHub URL. Do not forward a credential to an incoming URL chosen by the caller.

The adapter is new in the SDK source; the previous 0.10.0 release does not contain it. The runnable repository example uses the local SDK until a release containing seekrit.cloudflare is published. The Python Workers guide has the complete Pywrangler manifests, local setup, deployment commands, and a FastAPI route using Cloudflare's ASGI adapter.

Rotation without another application deploy

resolve() fetches on each call. Adding a key to Seekrit or rotating an existing value changes what the next handler receives; it does not require setting another Cloudflare binding or deploying application code again. Resolve once when a handler needs several keys, and reuse that mapping for the rest of the request.

The app can compose shared credentials from a group and override a tenant's environment slice:

secrets = await AsyncClient(
    token=self.env.SEEKRIT_TOKEN,
    overrides={"tenants": authenticated_tenant},
).resolve()

The override selects within the token's authorized composition. Choose it from authenticated application context, and keep the resulting values local to the request. A module-level tenant cache or os.environ.update(secrets) would turn that separation into shared state.

There is a tradeoff: a handler that resolves depends on Seekrit being reachable. On a refusal, timeout, or decryption failure, the client fails closed. An application that introduces a cache also introduces a delay before rotation and revocation are observed, so give that cache an explicit lifetime and decide what an API refusal does to it.

When bindings are the right fit

Runtime resolution is one choice. If an existing Python Worker already reads self.env.GITHUB_TOKEN and you want to keep that code, Seekrit's Cloudflare sync can push the environment into the Worker's secret bindings. Python and JavaScript read those bindings in the same way; this path needs no Python SDK change.

Sync requires an explicit decryption grant and acknowledgement because Seekrit decrypts for the push. Runtime resolution keeps decryption in the customer's Worker. In either case, code running with the credential can read it: this integration centralizes storage and access, rather than isolating a key from untrusted code in the same Worker.

For applications and agents executing untrusted code, the Cloudflare agent integration covers keeping keys in an egress handler outside the workload. For a trusted Python API, start with the Python Workers guide: one token, an async resolve, and credentials used only where the request needs them.