seekrit
Docs/Cloudflare Python Workers

Cloudflare Python Workers

Give a Python Worker one secret binding, SEEKRIT_TOKEN, and resolve the rest inside its handler with seekrit.cloudflare.AsyncClient. Seekrit returns ciphertext and wrapped data keys; the SDK decrypts inside your Worker. Adding or rotating an application credential in Seekrit does not require updating each Cloudflare binding.

Cloudflare announced Python Workers general availability on September 21, 2026. Python runs on Pyodide, and Pywrangler installs packages for that runtime. The Seekrit adapter uses native workers.fetch, while decryption uses the existing SDK's cryptography implementation and its Pyodide build.

note

The async adapter is new in the SDK source; the previous 0.10.0 release does not include it. Use a release containing seekrit.cloudflare, or run the repository example, which points at the local SDK through tool.uv.sources.

1. Create a service token

Create an app environment in Seekrit, add the credentials your Worker needs, and create a service token for that environment. The token needs permission to resolve secrets and key grants for the app and any composed groups. Use separate tokens for development, staging, and production.

SEEKRIT_TOKEN is the bootstrap credential. Store it as a Cloudflare secret, never in source or Wrangler's plaintext vars.

2. Set up the Python Worker

Install current uv (0.12.3 or newer) and Node.js 22 or newer. In a new directory, create pyproject.toml:

[project]
name = "seekrit-python-worker"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = ["seekrit"]

[dependency-groups]
dev = ["workers-py>=1.17.5", "workers-runtime-sdk>=1.9.2"]

Install Wrangler in the project so its version is explicit:

npm install --save-dev 'wrangler@^4.139.0'

Create wrangler.jsonc:

{
  "name": "seekrit-python-worker",
  "main": "src/entry.py",
  "compatibility_date": "2026-09-30",
  "compatibility_flags": ["python_workers"],
  "secrets": { "required": ["SEEKRIT_TOKEN"] },
  "observability": { "enabled": true }
}

The python_workers flag is still required after GA. Pywrangler resolves compatible packages and vendors them for deployment. Let it select the runtime's cryptography wheel; a native macOS or Linux wheel cannot run in the Worker.

3. Resolve inside the handler

Create src/entry.py:

from workers import Response, WorkerEntrypoint

from seekrit import SeekritError
from seekrit.cloudflare import AsyncClient


class Default(WorkerEntrypoint):
    async def fetch(self, request):
        if request.method != "GET":
            return Response("Method not allowed", status=405, headers={"allow": "GET"})
        try:
            secrets = await AsyncClient(
                token=getattr(self.env, "SEEKRIT_TOKEN", None)
            ).resolve()
        except SeekritError:
            return Response("Secret resolution unavailable", status=503)

        # Pass secrets["YOUR_API_KEY"] to your provider client here.
        # This setup check returns no secret names or values.
        return Response.from_json({"configured": bool(secrets)})

This handler verifies the integration without publishing a credential. Replace the success response with your application logic. Resolve once per handler and pass values directly to the clients that need them. get() is also async and performs a new resolve:

client = AsyncClient(
    token=self.env.SEEKRIT_TOKEN,
    timeout=10.0,
    overrides={"shared": "staging"},
)
api_key = await client.get("API_KEY")

Pass api_url="https://your-seekrit-api.example.com" for a self-hosted installation. interpolate=False returns stored reference text literally; otherwise the SDK expands secret references after merging the app and group layers.

The timeout covers the request and response body, and the client aborts the underlying fetch on timeout or cancellation. Redirects are refused. API refusals raise SeekritApiError with status and code; invalid grants or tampered ciphertext raise SeekritCryptoError. Any failure stops resolution without returning a partial set.

4. Run locally and deploy

Create a gitignored .dev.vars containing the development token:

SEEKRIT_TOKEN=<your development service token>

Then run:

uv run pywrangler dev

Open the local URL printed by Pywrangler. With a valid token and a nonempty environment, the example returns {"configured": true}. Keep .dev.vars, .venv-workers, and python_modules out of Git.

With Cloudflare authentication configured, deploy and add the production token through the interactive secret prompt:

uv run pywrangler deploy
uv run pywrangler secret put SEEKRIT_TOKEN

The example returns 503 until the token is installed. Test the deployed URL and confirm the setup response. Add or rotate a credential in Seekrit and make another request: this client has no plaintext cache, so the next resolve reads the current environment without an application redeploy.

For a named Wrangler environment, deploy and set the secret with the same environment flag. Repeat secrets.required in the named environment's config, because that setting is not inherited:

// Add to wrangler.jsonc
{
  "env": {
    "staging": {
      "name": "seekrit-python-worker-staging",
      "secrets": { "required": ["SEEKRIT_TOKEN"] }
    }
  }
}
uv run pywrangler deploy --env staging
uv run pywrangler secret put SEEKRIT_TOKEN --env staging

FastAPI

Add FastAPI to your project's dependencies:

uv add fastapi

Cloudflare's ASGI adapter puts Worker bindings in the request scope. Resolve from that request's environment:

from fastapi import FastAPI, HTTPException, Request
from workers import asgi

from seekrit import SeekritError
from seekrit.cloudflare import AsyncClient

app = FastAPI()


@app.get("/ready")
async def ready(request: Request):
    env = request.scope["env"]
    try:
        secrets = await AsyncClient(
            token=getattr(env, "SEEKRIT_TOKEN", None)
        ).resolve()
    except SeekritError:
        raise HTTPException(status_code=503, detail="Secret resolution unavailable")
    return {"configured": bool(secrets)}


Default = asgi.entrypoint(app)

Keep resolution in an async route. seekrit.Client, seekrit.load(), and into_env() are the process-oriented APIs. The existing AsyncSeekritTransport HTTP adapters still resolve through a thread executor; Cloudflare's threading module is not functional in Python Workers. Use seekrit.cloudflare.AsyncClient here.

Values, rotation, and the sync alternative

Resolved credentials are plaintext in the Worker's memory, readable by code running in that isolate. Keep them out of logs, responses, shared state, and persistent storage. Avoid loading os.environ: it is shared process state, which is especially risky when requests select different tenant overrides. Derive any tenant selection from authenticated application context.

Revoking the service token prevents future resolves; it cannot erase values already copied by the application. Rotate exposed credentials at their issuers. If you add your own cache, its lifetime also determines when a secret rotation or access refusal becomes visible to the application.

If you want credentials provided as ordinary Python Worker bindings instead, sync to Cloudflare and read self.env.API_KEY. That path works independently of the async SDK adapter. It uses Seekrit's explicit server-side decryption grant to push plaintext into Cloudflare; runtime resolution decrypts in your Worker and needs only its service token.