# Authentication, authorization and evidence privacy

Nonfinal architecture review · 0.4.0. Generated from `assets/fixtures-security.js`; edit that source and rerun `node tools/generate-docs.js`.

OAuth2 client credentials after operator provisioning. Canonical scopes: captures:write, artifacts:write, receipts:read, workers:heartbeat, workers:register. Stable instance sub and worker binding survive credential rotation. Required source selectors and targets are checked against provisioned grants.

Proposed: OIDC login with server session __Host-prw_session; Secure; HttpOnly; SameSite=Lax; Path=/; no Domain. Mutations require matching Origin and server synchronizer token in X-CSRF-Token.

Cryptography is simulated. Scope checks, worker/instance attribution, stable receipt ownership and CSRF decisions execute through the shared handler. Rotation changes credentials, not receipt ownership.

## recommendation

### label

PROP

### text

One IdP (or a small token service) issues: OIDC sessions for operators, and OAuth2 client-credentials JWTs for each collector instance, validated in Workers via JWKS with tenant + sources claims and a credential-registry revocation check. Cloudflare Access may sit in front of the admin origin as an outer gate. The custom HMAC proposal is retired.

### consequences

- contracts.html worker auth notes (owned by P2) should switch headers from X-PRW-* to Authorization: Bearer — requested in the P3 report
- Error codes: 401 INVALID_TOKEN, 401 CREDENTIAL_REVOKED, 403 PERMISSION_DENIED, 403 TENANT_SCOPE_DENIED, 403 SOURCE_SCOPE_DENIED
- IdP choice is OPEN (managed OIDC provider vs. small self-hosted token service)

## permissions

### id

captures:write

### label

Submit capture

### api

POST /v1/captures

### id

artifacts:write

### label

Upload artifact

### api

evidence object write

### id

receipts:read

### label

Read receipt

### api

GET /v1/receipts/{id}

### id

workers:heartbeat

### label

Heartbeat

### api

POST /v1/workers/{id}/heartbeat

### id

workers:register

### label

Register provisioned instance

### api

POST /v1/workers/register

### id

reviews:read

### label

Read review

### api

GET /v1/review…

### id

decisions:write

### label

Decide

### api

POST /v1/decisions

### id

attributes:publish

### label

Define attributes

### api

attribute definition approval

### id

replays:write

### label

Trigger replay

### api

POST /v1/improvements/{id}/replays

### id

events:publish

### label

Publish events

### api

outbox → Queues producer

### id

events:consume

### label

Consume events

### api

Queues consumer

### id

projections:read

### label

Read projections

### api

GET /v1/items…

### id

projections:write

### label

Write projections

### api

product DB write

### id

evidence:read

### label

Read evidence objects

### api

R2 evidence read

### id

models:invoke

### label

Call model provider

### api

outbound LLM API

### id

secrets:read

### label

Read secrets

### api

secret bindings

### id

exports:read

### label

Export snapshot

### api

tenant export

### id

publications:write

### label

Approve public release

### api

POST /v1/publications

### id

identity-generations:activate

### label

Activate identity generation

### api

POST /v1/identity-generations/{id}/activate

## principals

### id

prn_operator_reviewer

### name

Operator — reviewer

### kind

human

### authn

OIDC session (cred_operator_oidc_session)

### tenant Source

tenant membership claim in session

### label

PROP

### grants

#### receipts:read

S

#### reviews:read

S

#### decisions:write

S

#### replays:write

S

#### projections:read

Y

#### evidence:read

S

### notes

#### receipts:read

own tenant only

#### reviews:read

own tenant only

#### decisions:write

own tenant; against expected case revision; CSRF token required

#### replays:write

dry-run replay only; promotion needs admin

#### evidence:read

sanitized, script-free view served by resolver; never raw HTML

### id

prn_operator_admin

### name

Operator — admin

### kind

human

### authn

OIDC session + step-up (OPEN)

### tenant Source

tenant membership claim in session

### label

PROP

### grants

#### receipts:read

S

#### reviews:read

S

#### decisions:write

S

#### attributes:publish

S

#### replays:write

S

#### projections:read

Y

#### evidence:read

S

#### exports:read

S

#### publications:write

S

#### identity-generations:activate

S

### notes

#### receipts:read

own tenant

#### reviews:read

own tenant

#### decisions:write

own tenant

#### attributes:publish

Publish definition within the private catalog. Identity activation and public release are separate permissions.

#### replays:write

own tenant; can promote

#### evidence:read

sanitized view

#### exports:read

own tenant only; audited

#### secrets:read

admins manage credentials through the IdP / deploy pipeline, never by reading secret values

### id

prn_collector_instance

### name

Collector instance

### kind

machine

### authn

OAuth2 client credentials, one client per instance (cred_ci_*)

### tenant Source

tenant claim in JWT

### label

PROP

### grants

#### captures:write

S

#### artifacts:write

S

#### receipts:read

S

#### workers:heartbeat

S

#### workers:register

S

### notes

#### captures:write

token tenant + assigned sources only

#### artifacts:write

source_id selects a granted source; worker/instance attribution and tenant object prefix are derived from authenticated context.

#### receipts:read

Stable instance ownership survives client credential rotation; another instance is denied even in the same tenant/source.

#### workers:heartbeat

its own instance id only

#### evidence:read

collectors write evidence, never read other evidence

#### workers:register

Provisioned worker and stable instance only; requested targets must be a subset of server source grants.

### id

prn_resolver_service

### name

Resolver service

### kind

service

### authn

Workers runtime identity + bindings (no bearer secret inside the account)

### tenant Source

propagated from the authenticated request / job row

### label

PROP

### grants

#### artifacts:write

Y

#### receipts:read

Y

#### reviews:read

Y

#### decisions:write

S

#### replays:write

S

#### evidence:read

Y

#### models:invoke

S

#### secrets:read

S

### notes

#### decisions:write

deterministic rules only; human decisions are recorded on behalf of the authenticated operator

#### replays:write

executes replays an operator requested

#### models:invoke

extraction worker only

#### secrets:read

its own bindings (DB role resolver_app, model key in extraction worker)

#### events:publish

writes the outbox row; publishing is the relay

### id

prn_relay

### name

Outbox relay

### kind

service

### authn

Workers binding (Queues producer) + DB role resolver_app (outbox read/mark)

### tenant Source

tenant_id column on outbox rows

### label

PROP

### grants

#### events:publish

Y

#### secrets:read

S

### notes

#### secrets:read

Queues producer binding and outbox DB access only

### id

prn_product_service

### name

Product service

### kind

service

### authn

Workers runtime identity + bindings

### tenant Source

tenant_id inside each accepted-change event

### label

PROP

### grants

#### events:consume

Y

#### projections:read

Y

#### projections:write

Y

#### secrets:read

S

### notes

#### secrets:read

DB role product_app only

#### evidence:read

serves original URLs and observed_at; never reads evidence objects

### id

prn_consumer_anonymous

### name

Consumer (anonymous)

### kind

human

### authn

none (public read API, rate-limited)

### tenant Source

tenant fixed by route/host (OPEN)

### label

PROP

### grants

#### projections:read

S

### notes

#### projections:read

published projections only; rate-limited; no operator fields

### id

prn_llm_extractor

### name

LLM extractor

### kind

llm

### authn

none — not a principal with a credential; runs inside the extraction worker

### tenant Source

n/a (receives a single-tenant excerpt)

### label

EST

### grants

### notes

#### evidence:read

receives a bounded excerpt chosen by the worker; no object access

#### decisions:write

output is a proposal (claims) only

#### secrets:read

never sees keys

#### models:invoke

the worker calls the model, not the model itself

#### captures:write

cannot fetch URLs or create captures

### id

prn_llm_reviewer

### name

LLM reviewer

### kind

llm

### authn

none — not a principal with a credential

### tenant Source

n/a

### label

EST

### grants

### notes

#### reviews:read

receives the case excerpt from the worker; cannot query

#### decisions:write

recommendation only; cannot accept

#### secrets:read

never

### id

prn_deploy_pipeline

### name

Deploy pipeline

### kind

machine

### authn

Scoped Cloudflare API token (cred_deploy_api_token) in CI secret store

### tenant Source

n/a (platform)

### label

PROP

### grants

#### secrets:read

S

### notes

#### secrets:read

write-only: sets Worker secrets and bindings; cannot read values back

#### exports:read

never exports tenant data

#### decisions:write

runs migrations with the migrator DB role, no business decisions

## inventory

### id

cred_operator_oidc_session

### name

Operator OIDC session

### kind

session

### principal

prn_operator_reviewer / prn_operator_admin

### owner

Resolver admin origin

### storage

__Host-prw_session cookie: HttpOnly; Secure; SameSite=Lax; Path=/; no Domain. Opaque server session with synchronizer CSRF token sent in X-CSRF-Token for mutations.

### scope

one user, tenant memberships + roles from IdP claims

### lifetime

8 h idle 30 min (PROP)

### rotation

new session id on login and on privilege change

### revocation

delete session row; IdP logout; disable user at IdP

### detection

login audit, impossible-travel alerts at IdP (OPEN)

### label

PROP

### id

cred_oidc_client_admin

### name

OIDC client (admin origin)

### kind

oauth_client

### principal

prn_resolver_service

### owner

Platform admin

### storage

Workers secret OIDC_CLIENT_SECRET in resolver admin Worker

### scope

authorization-code flow for operator login

### lifetime

until rotated

### rotation

every 180 days or on exposure; two secrets accepted during overlap

### revocation

rotate at IdP

### detection

IdP client usage logs

### label

PROP

### id

cred_ci_halden_fra_01

### instance

ci_halden_fra_01

### name

Collector client — Halden US (regional host)

### kind

oauth_client

### principal

prn_collector_instance

### owner

Collector operator (regional host)

### storage

host secret file / OS keychain on the regional host (0600)

### scope

tenant ten_primary · sources [src_halden_us] · scope captures:write receipts:read workers:heartbeat

### lifetime

client: until rotated · access token: 10 min

### rotation

90 days; issue new client, retire old

### revocation

disable client at IdP + denylist in resolver credential registry (immediate)

### detection

requests from unexpected source ids, token minting rate, egress country change

### label

PROP

### id

cred_ci_halden_de_cf

### instance

ci_halden_de_cf

### name

Collector client — Halden DE (Worker)

### kind

oauth_client

### principal

prn_collector_instance

### owner

Platform admin

### storage

Workers secret on the collector Worker

### scope

ten_primary · [src_halden_de]

### lifetime

access token 10 min

### rotation

90 days

### revocation

disable client + registry denylist

### detection

registry mismatch alerts

### label

PROP

### id

cred_ci_bazaarline_cf

### instance

ci_bazaarline_cf

### name

Collector client — Bazaarline API (Worker)

### kind

oauth_client

### principal

prn_collector_instance

### owner

Platform admin

### storage

Workers secret on the collector Worker

### scope

ten_primary · [src_bazaarline_api]

### lifetime

access token 10 min

### rotation

90 days

### revocation

disable client + registry denylist

### detection

25 consecutive failures already flagged; revocation demo target

### label

PROP

### id

cred_ci_pantrypour_cf

### instance

ci_pantrypour_cf

### name

Collector client — Pantry & Pour feed (Worker)

### kind

oauth_client

### principal

prn_collector_instance

### owner

Platform admin

### storage

Workers secret

### scope

ten_primary · [src_pantrypour_web]

### lifetime

access token 10 min

### rotation

90 days

### revocation

disable client + registry denylist

### detection

registry mismatch alerts

### label

PROP

### id

cred_ci_lumen_cf

### instance

ci_lumen_cf

### name

Collector client — Lumen browser (Worker)

### kind

oauth_client

### principal

prn_collector_instance

### owner

Platform admin

### storage

Workers secret

### scope

ten_primary · [src_lumen_web]

### lifetime

access token 10 min

### rotation

90 days

### revocation

disable client + registry denylist

### detection

registry mismatch alerts

### label

PROP

### id

cred_ci_synth_a_cf

### instance

ci_synth_a_cf

### name

Collector client — Synthetic A (synthetic)

### kind

oauth_client

### principal

prn_collector_instance

### owner

ten_synth_a (synthetic)

### storage

n/a — demo only

### scope

ten_synth_a · [src_synth_a_shop]

### lifetime

access token 10 min

### rotation

n/a

### revocation

n/a

### detection

n/a

### synthetic

true

### label

PROP

### id

cred_svc_resolver

### name

Resolver service identity

### kind

binding

### principal

prn_resolver_service

### owner

Deploy pipeline

### storage

none — Workers service bindings inside the account; an OAuth client only if a call ever leaves the account

### scope

bindings declared in wrangler config

### lifetime

deployment

### rotation

redeploy

### revocation

remove binding + redeploy

### detection

config review in CI

### label

PROP

### id

cred_svc_product

### name

Product service identity

### kind

binding

### principal

prn_product_service

### owner

Deploy pipeline

### storage

none — bindings

### scope

Queues consumer + DB role product_app

### lifetime

deployment

### rotation

redeploy

### revocation

remove binding

### detection

config review in CI

### label

PROP

### id

cred_queue_bindings

### name

Queues producer / consumer bindings

### kind

binding

### principal

prn_relay / prn_product_service

### owner

Deploy pipeline

### storage

wrangler bindings (no secret material)

### scope

producer: relay only · consumer: product ingestion only

### lifetime

deployment

### rotation

n/a

### revocation

remove binding

### detection

unexpected producer in CI diff

### label

PROP

### id

cred_db_resolver_app

### name

DB role resolver_app

### kind

db_role

### principal

prn_resolver_service / prn_relay

### owner

Platform admin

### storage

connection string as Workers secret (or Hyperdrive config)

### scope

resolver schema DML; no DDL; no product schema

### lifetime

until rotated

### rotation

90 days via migrator; dual-password overlap

### revocation

ALTER ROLE … NOLOGIN

### detection

pg audit / connection logs

### label

PROP

### id

cred_db_product_app

### name

DB role product_app

### kind

db_role

### principal

prn_product_service

### owner

Platform admin

### storage

Workers secret / Hyperdrive

### scope

product schema DML only (separate database or schema, OPEN)

### lifetime

until rotated

### rotation

90 days

### revocation

NOLOGIN

### detection

connection logs

### label

PROP

### id

cred_db_migrator

### name

DB role migrator

### kind

db_role

### principal

prn_deploy_pipeline

### owner

Platform admin

### storage

CI secret store only

### scope

DDL on both schemas; used only by migration jobs

### lifetime

until rotated

### rotation

90 days

### revocation

NOLOGIN

### detection

DDL outside deploy window → alert

### label

PROP

### id

cred_db_readonly

### name

DB role readonly

### kind

db_role

### principal

operators (break-glass analysis)

### owner

Platform admin

### storage

password manager; issued on request

### scope

SELECT, with row-level security on tenant_id (OPEN)

### lifetime

short (24 h) when issued

### rotation

per issue

### revocation

NOLOGIN

### detection

every session logged

### label

OPEN

### id

cred_r2_evidence_binding

### name

R2 evidence bucket binding

### kind

binding

### principal

prn_resolver_service

### owner

Deploy pipeline

### storage

Workers R2 binding (no key)

### scope

bucket prw-evidence; writes under evidence/{tenant}/…

### lifetime

deployment

### rotation

n/a

### revocation

remove binding

### detection

CI diff

### label

PROP

### id

cred_r2_export_key

### name

R2 S3-API key (backup/export)

### kind

api_key

### principal

backup job

### owner

Platform admin

### storage

CI secret store

### scope

read-only, one bucket

### lifetime

≤ 1 year

### rotation

yearly or on exposure

### revocation

delete key in dashboard

### detection

R2 access logs (OPEN)

### label

OPEN

### id

cred_model_provider_key

### name

Model-provider API key

### kind

api_key

### principal

prn_resolver_service (extraction worker only)

### owner

Platform admin

### storage

Workers secret on the extraction Worker only

### scope

one project; spend cap at provider

### lifetime

until rotated

### rotation

90 days; provider supports multiple keys for overlap (verify per provider)

### revocation

delete key at provider

### detection

spend alerts, unexpected model ids

### label

PROP

### id

cred_deploy_api_token

### name

Deployment API token

### kind

api_key

### principal

prn_deploy_pipeline

### owner

Platform admin

### storage

CI secret store (environment-protected)

### scope

Workers scripts, secrets, Queues, R2 bindings for one account; no DNS/billing

### lifetime

90 days

### rotation

90 days

### revocation

revoke token in dashboard

### detection

account audit log

### label

PROP

### id

cred_access_service_token

### name

Cloudflare Access service token (optional)

### kind

service_token

### principal

regional/local collector hosts (outer gate only)

### owner

Platform admin

### storage

host secret file

### scope

passes the Access edge gate for the intake hostname; not a per-instance identity

### lifetime

chosen duration (e.g. 1 year)

### rotation

rotate secret with grace period (Client ID unchanged)

### revocation

delete the token

### detection

Access logs

### label

OPEN

### id

cred_ci_halden_fra_02

### instance

ci_halden_fra_02

### name

Second Halden instance (fixture)

### kind

oauth_client

### principal

prn_collector_instance

### owner

Collector operator (regional host)

### storage

host secret file / OS keychain on the regional host (0600)

### scope

tenant ten_primary · sources [src_halden_us] · scope captures:write receipts:read workers:heartbeat

### lifetime

client: until rotated · access token: 10 min

### rotation

90 days; issue new client, retire old

### revocation

disable client at IdP + denylist in resolver credential registry (immediate)

### detection

requests from unexpected source ids, token minting rate, egress country change

### label

PROP

### id

cred_ci_synth_b_cf

### instance

ci_synth_b_cf

### name

Collector client — Synthetic B

### kind

oauth_client

### principal

prn_collector_instance

### owner

ten_synth_b (synthetic)

### storage

n/a — demo only

### scope

ten_synth_b · [src_synth_b_feed]

### lifetime

access token 10 min

### rotation

n/a

### revocation

n/a

### detection

n/a

### synthetic

true

### label

PROP

## tls

### link

Operator browser → admin origin

### server

Managed edge TLS

### client

Browser trust store

### mtls

No

### label

PROP

### link

Consumer browser → product API

### server

Managed edge TLS

### client

Browser trust store

### mtls

No

### label

PROP

### link

Workers collectors → resolver intake

### server

Managed edge TLS

### client

Workers runtime trust

### mtls

No

### label

PROP

### link

Local / regional collectors → resolver intake

### server

Managed edge TLS

### client

Standard OS trust validation; never disable verification

### mtls

Not required initially; Access mTLS possible for regional hosts

### label

OPEN

### link

Collectors → source sites

### server

Source’s own TLS

### client

Standard validation; no pinning

### mtls

No

### label

PROP

### link

Resolver extraction → model provider

### server

Provider TLS

### client

Workers runtime trust

### mtls

No

### label

PROP

### link

Workers → PostgreSQL

### server

Provider TLS (sslmode=verify-full or Hyperdrive)

### client

Validate server cert

### mtls

No (password role + TLS)

### label

PROP

### link

Workers → R2 / Queues

### server

Bindings (in-platform)

### client

n/a

### mtls

n/a

### label

PROP

## untrusted

### id

ui-1

### rule

Scraped HTML is never rendered as HTML in the operator UI. Show escaped text, or a sandboxed iframe (sandbox without allow-scripts, separate origin) for visual inspection.

### label

EST

### id

ui-2

### rule

Admin origin sends a strict CSP (default-src 'self'; script-src 'self'; frame-ancestors 'none'); evidence previews use a separate, script-less origin.

### label

PROP

### id

net-1

### rule

Collectors fetch only allowlisted origins of their assigned sources; redirects off-allowlist are dropped and recorded.

### label

PROP

### id

net-2

### rule

The resolver never fetches URLs found in evidence. Policy pages are captured by collectors, not by the resolver.

### label

PROP

### id

net-3

### rule

SSRF protections wherever a fetch exists: deny private, loopback, link-local and metadata ranges; resolve-then-check; cap size and time.

### label

PROP

### id

llm-1

### rule

LLM inputs are untrusted (prompt injection): evidence text is passed as data in delimited fields, never as instructions; no tools, no browsing.

### label

EST

### id

llm-2

### rule

Model output is a proposal only: schema-validated claims with confidence. It cannot authorize acceptance, change permissions or access secrets.

### label

EST

### id

llm-3

### rule

Model-provider keys exist only in the resolver extraction worker.

### label

PROP

### id

log-1

### rule

Tokens, cookies and secret headers are redacted from logs and from stored captures.

### label

PROP

## demos

### id

demo_cross_tenant_capture

### title

Cross-tenant capture submission

### expect

403

### group

tenant

### request

#### credential

auto:ci_halden_fra_01

#### action

captures:write

#### method

POST

#### path

/v1/captures

#### resource

src_synth_a_shop

#### body

##### tenant_id

ten_synth_a

##### worker_id

cw_halden_us_browser

##### instance_id

ci_halden_fra_01

##### source

##### source_id

src_synth_a_shop

##### native_record_id

QF-0001

##### original_url

https://shop.quillfeather.example.com/p/qf-0001

### id

demo_cross_tenant_receipt

### title

Cross-tenant receipt read

### expect

403

### group

tenant

### request

#### credential

auto:ci_halden_fra_01

#### action

receipts:read

#### method

GET

#### path

/v1/receipts/rcpt_synth_a_0001?tenant_id=ten_synth_a

#### resource

rcpt_synth_a_0001

#### body

### id

demo_same_tenant_capture

### title

Same-tenant capture (contrast)

### expect

202

### group

tenant

### request

#### credential

auto:ci_halden_fra_01

#### action

captures:write

#### method

POST

#### path

/v1/captures

#### resource

src_halden_us

#### body

##### tenant_id

ten_primary

##### worker_id

cw_halden_us_browser

##### instance_id

ci_halden_fra_01

##### source

##### source_id

src_halden_us

##### native_record_id

HLD-118204

##### original_url

https://shop.halden.example.com/us/p/hld-118204

### id

demo_operator_cross_tenant

### title

Synthetic-B reviewer reads a primary case

### expect

403

### group

tenant

### request

#### credential

session:usr_synth_b_lee

#### action

reviews:read

#### method

GET

#### path

/v1/review/rc_0007

#### resource

rc_0007

#### body

### id

demo_revoked_heartbeat

### title

Heartbeat from Bazaarline collector

### expect

depends on revocation

### group

revocation

### request

#### credential

auto:ci_bazaarline_cf

#### action

workers:heartbeat

#### method

POST

#### path

/v1/workers/ci_bazaarline_cf/heartbeat

#### resource

ci_bazaarline_cf

#### body

##### instance_id

ci_bazaarline_cf

##### run

##### outcome

failed

##### error

```typescript
UPSTREAM_503
```

##### retryable

true

### id

demo_revoked_capture

### title

Capture from Bazaarline collector

### expect

depends on revocation

### group

revocation

### request

#### credential

auto:ci_bazaarline_cf

#### action

captures:write

#### method

POST

#### path

/v1/captures

#### resource

src_bazaarline_api

#### body

##### tenant_id

ten_primary

##### worker_id

cw_bazaarline_api

##### instance_id

ci_bazaarline_cf

##### source

##### source_id

src_bazaarline_api

##### native_record_id

OFR-55120-NF

##### original_url

https://api.bazaarline.example.com/v2/offers/OFR-55120-NF

## alternatives

### id

oidc_operator

### name

Operator OIDC login

### for

Operators

### label

PROP

### verdict

adopt

### how

Authorization-code flow with PKCE against one IdP. Admin origin creates an opaque server session; cookie HttpOnly; Secure; SameSite=Lax. State-changing admin requests require a CSRF token (synchronizer or double-submit) in a header.

### pros

Standard, MFA and offboarding live in the IdP; tenant + role claims map directly to authorization.

### cons

Needs an IdP account and redirect configuration.

### revocation

Delete session; disable user at IdP.

### id

oauth_cc

### name

OAuth2 client credentials per collector instance

### for

Collectors (Workers, local, regional)

### label

PROP

### verdict

adopt

### how

Each collector instance has its own client. It requests a short-lived JWT access token (≈10 min) and sends Authorization: Bearer. Workers validate via cached JWKS: iss, aud, exp, nbf, scope, tenant claim, sources claim; then check the credential registry for revocation.

### pros

Same mechanism from Workers, local processes and regional hosts; standard libraries; per-instance revocation; scopes and tenant in claims.

### cons

Token endpoint becomes a dependency (cache tokens; collectors retry). Custom claims (tenant, sources) need IdP support or a small token service.

### revocation

Disable client at IdP + registry denylist (immediate); outstanding tokens die within TTL anyway.

### id

cf_access

### name

Cloudflare Access service tokens

### for

Edge gate for admin origin / intake

### label

OPEN

### verdict

optional outer layer

### how

Client sends CF-Access-Client-Id and CF-Access-Client-Secret headers; Access lets the request through to the origin. Rotation generates a new Client Secret with a grace period (Client ID unchanged); revocation before expiry means deleting the token. Source: developers.cloudflare.com/cloudflare-one/identity/service-tokens/ (accessed 2026-10-12).

### pros

Blocks unauthenticated traffic before it reaches the Worker; no code.

### cons

Coarse: a token is not a per-instance tenant/source claim set the resolver can authorize on; long-lived static secret; ties collectors to Cloudflare edge config.

### revocation

Delete token (verify behaviour of in-flight sessions).

### id

hmac

### name

Custom HMAC — retired alternative

### for

Collectors

### label

PROP

### verdict

retire

### how

Comparison only. Per-instance signed requests add custom key distribution, replay handling and rotation complexity. No active endpoint requires this scheme.

### pros

Portable; no token endpoint; signs the body.

### cons

A custom crypto protocol to specify, implement twice and test; shared-secret distribution and storage on both sides; no standard rotation/revocation tooling; replay-window and clock-skew handling must be invented; no standard claims for tenant/scope.

### revocation

Custom: delete key id from resolver table.

