This project is experimental / proof-of-concept. It is under active development and has not been audited. Do not use it to protect production systems without independent review.
Email: security@notme.bot
Please include:
- Description of the vulnerability
- Steps to reproduce
- Impact assessment
We will acknowledge reports within 48 hours and provide a timeline for resolution.
notme is an identity authority that issues ephemeral bridge certificates. Security-relevant components:
- SigningAuthority DO — Ed25519 CA key generation and storage (born in CF, never exported)
- Passkey/WebAuthn — registration and authentication flows
- DPoP token issuance — RFC 9449 sender-constrained tokens
- Bridge cert minting — X.509 certificate generation and signing
- Revocation — epoch-based CA key rotation
- GHA OIDC exchange — GitHub Actions token validation
- Secretless — private keys exist only in process memory (
extractable: falseCryptoKey). No key material on disk, in$GITHUB_OUTPUT, or crossing the wire. Seedocs/design/007-secretless-local-proxy.md. - Two enforcement planes — local workerd holds credentials and enforces scope; CF edge validates independently. Neither trusts the other.
- Proof-of-possession over bearer tokens — DPoP binding makes stolen tokens useless without the proof key.
- Ephemeral by default — 5-minute token/cert TTL, no renewal, just expire.
- Zero stored secrets — CA key born in Durable Object, session secrets auto-generated. In ephemeral mode (local/CI), the private JWK is never written to SQLite.
- Minimal blast radius — epoch-based revocation (one KV write revokes all certs from an epoch).
| Mode | When | Private key on disk? |
|---|---|---|
ephemeral |
Local dev, CI | No — in-memory only, dies with process |
encrypted |
Self-hosted (not yet implemented) | Wrapped with HKDF-derived KEK |
cf-managed |
Production CF Workers | CF manages DO SQLite encryption |
Set via NOTME_KEY_STORAGE env var. Default: cf-managed. Local workerd config sets ephemeral.
- Server-side threat model:
worker/THREAT_MODEL.md. Each row'stestcolumn maps to a named test inworker/src/__tests__/. - Executable threat-model + cross-repo contract assertions:
worker/src/__tests__/threat-model.test.ts. Encodes the 11 consumer↔server contract assumptions surfaced by the 2026-05-16 security review (OIDC audience, trusted issuers, CABundle CBOR canonical bytes, revocation seqno monotonicity, DPoP proof shape, scope vocabulary, AuthService RPC surface, redirect-URI normalization, GHA fork-PR boundary, and AuthService per-session credential isolation). Cross-repo invariants needing server-produced fixtures are markedit.todoand name the fixture.
- WebAuthn implementation has not been independently audited
- DPoP token endpoint does not yet implement nonce mechanism (defense-in-depth)
encryptedkey storage mode is designed but not yet implemented (startup error if configured)- Older design-time threat tables live in
docs/design/007-secretless-local-proxy.md; canonical threat model isworker/THREAT_MODEL.md.
The TypeScript DOM lib does not yet enumerate Ed25519 as a valid SubtleCrypto
algorithm identifier, and @cloudflare/workers-types does not extend the
algorithm union for it. CF Workers' WebCrypto runtime nevertheless implements
Ed25519 fully (BoringSSL).
All production Ed25519 sign/verify call sites import the single constant
ED25519 from worker/src/platform.ts, which carries the as any cast and
documents the gap. This means:
- Reviewers see exactly one cast in production code rather than chasing a string literal across the codebase.
- When the lib types catch up, removing the workaround is a one-line change
in
platform.ts. - Tests retain their own inline casts because they construct distinct
algorithm objects per call (e.g.
importKeyrequires the object form,signaccepts the string form).
Tracked in bead notme-ae1030.