Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,23 @@ A Cloudflare Worker that proxies OpenPGP Web Key Directory (WKD) requests to Pro
## Architecture

Single-file Worker (`src/index.ts`) with no framework dependencies. Production
`DOMAINS` and routes stay in the Cloudflare dashboard. Do not put `DOMAINS`
in `wrangler.jsonc` `vars`. Tests set example domains in `vitest.config.ts`.
`DOMAINS` is a Cloudflare Worker **secret**, not a plain `vars` entry. Runtime
still reads `env.DOMAINS` as a comma-separated string; secrets bind the same
way. Dashboard routes stay out of git. Do not put `DOMAINS` in
`wrangler.jsonc` `vars`. Tests set example domains in `vitest.config.ts`.
Local `wrangler dev` reads `.dev.vars`.

README.md **Production `DOMAINS` secret** is the primary user-facing guidance
for operators and forks. Follow that section for how to set or rotate the
secret (dashboard Secrets or `wrangler secret put DOMAINS`), never commit the
value, and keep example.com / example.org / example.net only in tests and
`.dev.vars.example`.

## Key Files

- `src/index.ts` - The entire Worker implementation
- `test/index.spec.ts` - Tests using `@cloudflare/vitest-pool-workers`
- `wrangler.jsonc` - Wrangler config (`keep_vars`, no routes, no `DOMAINS` var)
- `wrangler.jsonc` - Wrangler config (`secrets.required` for `DOMAINS`, no `keep_vars`, no routes, no `DOMAINS` var)
- `.github/workflows/deploy.yaml` - GitHub CI only: lint, test, `cf:check`

## Commands
Expand All @@ -47,16 +55,17 @@ Cloudflare Workers Builds owns production deploy from `main`. GitHub Actions
validates only. Do not run `wrangler deploy`, remote DNS writes, or use
`CLOUDFLARE_API_TOKEN` from GitHub or a Cloud Agent.

The Builds deploy command is `pnpm deploy:cloudflare` (`wrangler deploy --keep-vars`).
The Builds deploy command is `pnpm deploy:cloudflare` (`wrangler deploy`).
`workers_dev` is false and `route` / `routes` are omitted so dashboard routes stay.
`keep_vars` only preserves dashboard vars that are absent from `wrangler.jsonc`.
Do not commit `DOMAINS` in `vars`, including example values. This public
repo has no GitHub `DOMAINS` secret. Dashboard `DOMAINS` is the runtime
source.
`wrangler.jsonc` lists `DOMAINS` under `secrets.required` so Builds fails closed
when the secret is missing. Do not commit `DOMAINS` in `vars`, including
example values. This public repo has no GitHub `DOMAINS` secret. Agents must
not run `wrangler secret put` or use `CLOUDFLARE_API_TOKEN`. Operators set or
rotate from a workstation with Mark's token using the README procedure.

Adding a domain is a workstation or dashboard procedure: update the `DOMAINS`
var, add the three route patterns, create the `openpgpkey` CNAME, and add a
root placeholder only when the zone has no A/AAAA/CNAME.
secret per README, add the three route patterns, create the `openpgpkey` CNAME,
and add a root placeholder only when the zone has no A/AAAA/CNAME.

## Code Standards

Expand Down
29 changes: 19 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ A Cloudflare Worker that proxies OpenPGP Web Key Directory (WKD) requests to Pro
## Architecture

Single-file Worker (`src/index.ts`) with no framework dependencies. Production
`DOMAINS` and routes stay in the Cloudflare dashboard. Do not put `DOMAINS`
in `wrangler.jsonc` `vars`. Tests set example domains in `vitest.config.ts`.
`DOMAINS` is a Cloudflare Worker **secret**, not a plain `vars` entry. Runtime
still reads `env.DOMAINS` as a comma-separated string; secrets bind the same
way. Dashboard routes stay out of git. Do not put `DOMAINS` in
`wrangler.jsonc` `vars`. Tests set example domains in `vitest.config.ts`.
Local `wrangler dev` reads `.dev.vars`.

README.md **Production `DOMAINS` secret** is the primary user-facing guidance
for operators and forks. Follow that section for how to set or rotate the
secret (dashboard Secrets or `wrangler secret put DOMAINS`), never commit the
value, and keep example.com / example.org / example.net only in tests and
`.dev.vars.example`.

## Key Files

- `src/index.ts` - The entire Worker implementation
- `test/index.spec.ts` - Tests using `@cloudflare/vitest-pool-workers`
- `wrangler.jsonc` - Wrangler config (`keep_vars`, no routes, no `DOMAINS` var)
- `wrangler.jsonc` - Wrangler config (`secrets.required` for `DOMAINS`, no `keep_vars`, no routes, no `DOMAINS` var)
- `.github/workflows/deploy.yaml` - GitHub CI only: lint, test, `cf:check`

## Commands
Expand All @@ -36,16 +44,17 @@ Cloudflare Workers Builds owns production deploy from `main`. GitHub Actions
validates only. Do not run `wrangler deploy`, remote DNS writes, or use
`CLOUDFLARE_API_TOKEN` from GitHub or a Cloud Agent.

The Builds deploy command is `pnpm deploy:cloudflare` (`wrangler deploy --keep-vars`).
The Builds deploy command is `pnpm deploy:cloudflare` (`wrangler deploy`).
`workers_dev` is false and `route` / `routes` are omitted so dashboard routes stay.
`keep_vars` only preserves dashboard vars that are absent from `wrangler.jsonc`.
Do not commit `DOMAINS` in `vars`, including example values. This public
repo has no GitHub `DOMAINS` secret. Dashboard `DOMAINS` is the runtime
source.
`wrangler.jsonc` lists `DOMAINS` under `secrets.required` so Builds fails closed
when the secret is missing. Do not commit `DOMAINS` in `vars`, including
example values. This public repo has no GitHub `DOMAINS` secret. Agents must
not run `wrangler secret put` or use `CLOUDFLARE_API_TOKEN`. Operators set or
rotate from a workstation with Mark's token using the README procedure.

Adding a domain is a workstation or dashboard procedure: update the `DOMAINS`
var, add the three route patterns, create the `openpgpkey` CNAME, and add a
root placeholder only when the zone has no A/AAAA/CNAME.
secret per README, add the three route patterns, create the `openpgpkey` CNAME,
and add a root placeholder only when the zone has no A/AAAA/CNAME.

## Code Standards

Expand Down
69 changes: 52 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,48 @@ This Worker intercepts those requests via Cloudflare route patterns and proxies

## Features

- Supports unlimited custom domains via a single environment variable
- Supports unlimited custom domains via a single Worker secret
- Handles both WKD direct (subdomain) and advanced (`.well-known` path) methods
- Dashboard-managed routes and `DOMAINS` so real domains stay out of this public repo
- Dashboard-managed routes and a `DOMAINS` Worker secret so real domains stay out of this public repo
- One-time DNS setup for `openpgpkey.*` subdomains
- 100% test coverage with Cloudflare Workers vitest integration
- Full observability: structured logging, traces, and logpush

## Production `DOMAINS` secret

This is the primary guidance for operators and anyone forking the Worker.

Production `DOMAINS` is a **Cloudflare Worker secret**, not a plain Wrangler `vars` entry and not a GitHub Actions secret. The Worker reads `env.DOMAINS` at runtime as a comma-separated list of hostnames (no spaces required). Secrets bind the same way as vars, so existing code does not change.

**Never commit the value.** Do not put real domains in git, in `wrangler.jsonc` `vars`, in docs examples, in tests, or in GitHub Actions secrets or variables. This public repository has no GitHub `DOMAINS` secret.

### Set or rotate the secret

Use either path. Do not put the value in the repo.

1. Cloudflare dashboard: Worker `wkd-proxy-worker` → Settings → Secrets → add or update `DOMAINS`.
2. Workstation CLI (account token on that machine only):

```bash
wrangler secret put DOMAINS
```

Paste the comma-separated list when prompted, for example:

```text
example.com,example.org,example.net
```

That example is documentation only. Production uses your real custom domains in the secret, never in git.

`wrangler.jsonc` lists `DOMAINS` under `secrets.required`. Workers Builds (`pnpm deploy:cloudflare`, which is `wrangler deploy`) fails closed if the secret is missing. A Worker that somehow ran without it would return HTTP 500 when `env.DOMAINS` is empty.

If `DOMAINS` still exists as a dashboard **plain** variable, convert it to a **secret** before the next Builds deploy. Secrets survive deploys. Leftover plains do not, because this project no longer uses `keep_vars`.

### Example domains only in tests and local files

Tests (`vitest.config.ts`, `test/index.spec.ts`) and `.dev.vars.example` use only `example.com`, `example.org`, and `example.net`. Copy `.dev.vars.example` to `.dev.vars` for local `wrangler dev`. Never copy production domains into those files.

## Quick Start

### 1. Fork This Repository
Expand All @@ -40,10 +75,7 @@ In the Cloudflare dashboard, open Worker `wkd-proxy-worker` → Settings →
Builds and connect this repository. Production branch: `main`. Deploy
command: `pnpm deploy:cloudflare`. Leave non-production branch builds off.

Set the Worker runtime variable `DOMAINS` in Settings → Variables to a
comma-separated list of your custom domains. That list stays in the
Cloudflare dashboard. This public repository has no GitHub secret or
variable for `DOMAINS`, and GitHub Actions has no Cloudflare deploy token.
Set production `DOMAINS` as a Worker secret using [Production `DOMAINS` secret](#production-domains-secret). GitHub Actions has no Cloudflare deploy token.

### 4. Add routes and DNS once

Expand All @@ -60,10 +92,9 @@ has no A, AAAA, or CNAME (common for email-only domains), add proxied
placeholders `192.0.2.1` and `100::` so Cloudflare can intercept
`.well-known` requests. Existing website records stay untouched.

`wrangler.jsonc` sets `workers_dev = false` and `keep_vars = true` and
omits `routes` and `vars`. Wrangler still applies any `vars` that are in
the config, so example `DOMAINS` must not appear there. Tests and local
dev supply example domains separately.
`wrangler.jsonc` sets `workers_dev = false` and `secrets.required` to
`["DOMAINS"]` and omits `routes` and `vars`. Wrangler still applies any
`vars` that are in the config, so `DOMAINS` must not appear there.

### 5. Push to Deploy

Expand All @@ -85,7 +116,8 @@ pnpm run dev
```

This starts a local dev server. Copy `.dev.vars.example` to `.dev.vars`
so local `DOMAINS` uses the example list.
so local `DOMAINS` uses the example list (`example.com`, `example.org`,
`example.net`). See [Production `DOMAINS` secret](#production-domains-secret).

## Testing

Expand All @@ -96,12 +128,16 @@ pnpm run typecheck # TypeScript strict mode
pnpm run lint # ESLint strict type-checked
```

Tests inject the same example list. They never use production domains.

## How Deployment Works

GitHub Actions validates the pull request. Cloudflare Workers Builds deploys
from `main` with `pnpm deploy:cloudflare`. That command is
`wrangler deploy --keep-vars`. Dashboard routes stay because `routes` is
omitted. Production `DOMAINS` stays because it is not in `wrangler.jsonc`.
`wrangler deploy`. Dashboard routes stay because `routes` is omitted.

Production `DOMAINS` is a Worker secret. Set and rotate it as described in
[Production `DOMAINS` secret](#production-domains-secret).

The Worker only intercepts the three WKD route patterns. Other hostname
traffic is unchanged.
Expand All @@ -113,14 +149,13 @@ That org-required workflow uses hosted runners and no MNPPI secrets.

## Adding or Removing Domains

1. Update the Worker `DOMAINS` variable in the Cloudflare dashboard.
1. Update the Worker `DOMAINS` secret (dashboard Secrets or
`wrangler secret put DOMAINS`). Never commit the value. See
[Production `DOMAINS` secret](#production-domains-secret).
2. Add or remove the three route patterns for that domain.
3. Add or delete the `openpgpkey.*` CNAME. Add a root placeholder only when
the zone has no A, AAAA, or CNAME.

Do not put real domains in git, in `wrangler.jsonc` `vars`, or in GitHub
Actions secrets. Production `DOMAINS` is a Cloudflare dashboard variable.

## Prerequisites

- Custom domains added to Cloudflare (DNS managed by Cloudflare)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"scripts": {
"deploy": "echo 'Refusing workstation production deploy. Workers Builds owns pnpm deploy:cloudflare.' >&2; exit 1",
"deploy:cloudflare": "wrangler deploy --keep-vars",
"deploy:cloudflare": "wrangler deploy",
"cf:check": "wrangler deploy --dry-run",
"dev": "wrangler dev",
"start": "wrangler dev",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ overrides:
browserslist@<4.28.7: 4.28.7
esbuild@>=0.27.3 <0.28.1: 0.28.1
flatted@<3.4.2: ^3.4.2
fast-uri@>=3.0.0 <3.1.5: ^3.1.5
# fast-uri ≥3.1.7 floor for GHSA-58mr-gqgx-xq4g / GHSA-qw65-cvwx-89v3 — REMOVE when lockfile resolves ≥3.1.7 without this pin and those alerts are gone.
fast-uri@>=3.0.0 <3.1.7: 3.1.7
# hono ≥4.13.5 floor for GHSA-crvj-82cr-hjcx / GHSA-gqvv-2mrq-wpjv — REMOVE when lockfile resolves ≥4.13.5 without this pin and those alerts are gone.
hono@<4.13.5: 4.13.5
ip-address@<10.3.1: ^10.3.1
Expand Down
4 changes: 3 additions & 1 deletion wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
"main": "src/index.ts",
"compatibility_date": "2025-03-16",
"workers_dev": false,
"keep_vars": true,
"logpush": true,
"secrets": {
"required": ["DOMAINS"],
},
"observability": {
"enabled": true,
"head_sampling_rate": 1,
Expand Down
Loading