# 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](https://blog.cloudflare.com/python-workers-ga/). Python runs on Pyodide,
and [Pywrangler](https://developers.cloudflare.com/workers/languages/python/)
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](https://github.com/mileszim/seekrit/tree/main/sdks/python/examples/cloudflare-workers), 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](/docs/guides/service-tokens) 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](https://docs.astral.sh/uv/getting-started/installation/)
(0.12.3 or newer) and Node.js 22 or newer. In a new directory, create
`pyproject.toml`:

```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:

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

Create `wrangler.jsonc`:

```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](https://developers.cloudflare.com/workers/languages/python/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`:

```python
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:

```python
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:

```text
SEEKRIT_TOKEN=<your development service token>
```

Then run:

```bash
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:

```bash
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:

```jsonc
// Add to wrangler.jsonc
{
  "env": {
    "staging": {
      "name": "seekrit-python-worker-staging",
      "secrets": { "required": ["SEEKRIT_TOKEN"] }
    }
  }
}
```

```bash
uv run pywrangler deploy --env staging
uv run pywrangler secret put SEEKRIT_TOKEN --env staging
```

## FastAPI

Add FastAPI to your project's dependencies:

```bash
uv add fastapi
```

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

```python
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](https://developers.cloudflare.com/workers/languages/python/stdlib/).
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](/docs/guides/third-party-sync/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.
