Preview — not for production reliance
Documentation

Docs More

Security and trust model

What the directory vouches for, the controls behind each claim, the open items before production reliance, and how to report a vulnerability.

The trust model in one paragraph

Relying parties trust the directory as a verifier of organization-to-issuer bindings, and they authenticate it with keys they pinned out of band. A signature proves only that the directory made a statement; the evidence and policy behind the statement determine how much it is worth. The directory never issues credentials, holds no private keys of yours, and a lookup never grants access: authorization stays with the relying party.

Controls

Signed resolution

  • Compact JWS with an allow-list of one algorithm (ES256), a fixed typ (art-resolution+jwt;v=1) and only alg, kid and typ in the header. Embedded keys and key URLs (jku, jwk, x5u, x5c) are refused by the reference verifiers.
  • Lifetime at most 300 seconds, starting from the authoritative database read rather than the signing time, and shortened to the earliest expiry of everything the binding depends on. Resolution reads current primary state; responses are no-store.
  • A withdrawal is acknowledged only after it commits; from then on no active assertion is produced. Worst-case acceptance afterwards is 330 seconds (300 s plus 30 s verifier skew).
  • The signed payload is canonical JSON with a strict schema: unknown fields, a different bundle_url origin or an unsupported profile fail closed.

The signer

  • A separate Worker reachable only through a service binding; it has no HTTP routes and is never published to workers.dev.
  • It signs only assertion payloads, which it re-validates against the schema itself. There is no arbitrary-message signing, and callers cannot influence the JWS header.
  • Rotated keys are kept as public-only verify keys for at least 330 seconds and then retired; a compromised key is removed at once. Every kid is the RFC 7638 thumbprint of its key.

Accounts and sessions

  • Sign-in with GitHub or Google (authorization code with PKCE and a nonce), plus a passkey on every session before any change. Sensitive actions need a passkey assertion at most 300 seconds old.
  • __Host- cookies (HttpOnly, Secure, SameSite=Lax), a CSRF token and an exact Origin check on every mutation.
  • Service tokens are stored as hashes, scoped to one organization and to non-owner roles, expire within 90 days, cannot step up and cannot perform human-only operations.
  • Every object is authorized per tenant. Unknown, other-tenant and invisible records all answer the same 404.
  • Every mutation carries an Idempotency-Key and, where records change, an expected_version.

Claims, review and issuer control

  • DNS challenges are random, single-use, expire after 24 hours, are stored as hashes and are bound to the account, organization, domain and claim. Two public resolvers must both see the record at the exact name; answers through a CNAME or DNAME never count.
  • Identity and authority are reviewed by people. Representative authority needs an independent contact recorded by a reviewer; company email alone is refused.
  • Reviewers work in a separate console behind Cloudflare Access and must also hold the reviewer role. They cannot approve their own claim, cannot belong to an organization, and conflict-of-interest checks span every account that shares their email. Evidence downloads are audited.
  • Issuer control is proven without exporting CA keys: the federation endpoint must publish a challenge key, and every CA must mint a challenge SVID.
  • Production activation needs two different reviewers approving the exact bundle digest. Removing an old CA needs a reviewer who did not approve the step that added the new one; emergency rotations need two.

Integrity

  • State changes, audit records and outbox events are committed together in one D1 batch. Conflicting writes answer 409.
  • Database triggers keep binding versions immutable: no updates to key material, no deletes, no replacement. Bundle bytes go to create-only storage and are re-hashed against the approved digest on every request; a mismatch is never served.
  • Withdrawn and expired bindings cannot change state again. Change-feed events cannot be modified; only dispatched public events older than 90 days are pruned.

The scanner

  • TLS observations and federation-endpoint fetches run in an isolated container. Destinations must be public: loopback, private, link-local and metadata ranges are refused, and the connected address is re-checked at dial time to defeat DNS rebinding.
  • Port 443 only, at most 3 redirects, capped sizes and deadlines. Requests and responses are HMAC-authenticated, and each response is bound to the exact request it answers.
  • Observations are historical records. They never transfer identity, and redirects never do either.

The website

Every page is served with a strict Content Security Policy (script-src 'self', no inline scripts or styles), HSTS, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and X-Frame-Options: DENY.

Open items

These are known and tracked. They are why the site carries the Preview banner.

Item Status
Independent human security review of the assertion semantics, signing controls and representative verification Not done. A launch gate.
Managed KMS or HSM signer Not done. The prototype signer keeps its ES256 key in a Worker secret, which is not an HSM. A launch gate.
Signer access Any Worker in the same Cloudflare account could bind to the prototype signer. Its signatures still cover only schema-valid assertions, but production must restrict signing to the directory.
Signing audit The prototype signer does not log signatures.
Directory key distribution No published out-of-band channel yet for relying parties to receive directory key updates.
Scanner replay window A captured scanner request can be replayed within 120 seconds. Accepted: the channel is private, jobs are deduplicated, and a replay only repeats a read-only observation.
Profiles Only spiffe-x509 is supported. JWT-SVID and WIMSE profiles are not.

Reporting a vulnerability

Email security@svid.io.

TBD: security@svid.io is a placeholder. The security contact, a disclosure policy and response times have not been published yet.

Include the affected URL or component, the steps to reproduce, and any x-request-id values from the responses you saw.