Threat model, session/crypto design, and the constraints carried over from the widespread auth port.
- DID sessions (
dg_session): httpOnly cookie, HMAC-signed JWT (SESSION_SECRET), 24h TTL. Revocation authority is thedid_sessionsD1 row (revoked_at), checked on every authed request — logout and compromise revocation are immediate, not TTL-bound. - OAuth browser sign-in:
GET /auth/oauth/startruns the atproto OAuth profile as a public client (no client secret): PAR + PKCE(S256) + DPoP, with per-request keys generated fresh and stashed in KV under a single-useoauthstate:nonce (10-min TTL, deleted on callback). The authorization server is resolved per-account — handle → DID →#atproto_pdsservice →oauth-protected-resourcemetadata — defaulting tobsky.social. We request the minimalatprotoscope and discard the access token; only the token responsesubDID feeds the session bootstrap. - Challenge flow:
GET /auth/did/challengeissues a single-use KV nonce (5-min TTL);POST /auth/did/verifyconsumes it before signature verification — replay-safe by construction. Kept for did:key/device-key sign-in and agents. - DID document resolution:
did:plc→ plc.directory,did:web→/.well-known/did.json(unauthenticated canonical sources; the PDSresolveDidXRPC is a fallback — bsky.social gates it behind auth). - Signature verification: secp256k1 via
@noble/curves, ed25519 via WebCrypto. DID resolution (PLC directory +com.atproto.identity.resolveHandle,ATP_PDS_URL-configured PDS) is verified against the resolved DID document, not the claimed key. - Device keys: session-bound key rotation via
POST /auth/did/keys(bind/revoke).identities.device_keysis an append-only JSON audit log — revocations recordrevokedAt, entries are never deleted. - Tessera: legacy OIDC path, gated by
TESSERA_AUTH(offdefault). The OIDC client secret is only required when the flag is on. - DPoP:
did_sessions.dpop_jktstores the session's DPoP key thumbprint; sender-constrained clients bind their session to a key the server never sees.
- ed25519 signed envelopes (
x-dg-*headers) over a canonical digest — method, path, timestamp, nonce, body hash. Timestamp+nonce windows reject replays; nonces are KV-tracked. - Agent DIDs are
did:key(legacydid:dgread-compat). Server stores public keys only; private keys never leave the agent. - Reputation gates quorum voting and work claiming; rep is earned, not granted.
- No server-side atproto custody (widespread constraint carried over):
no atproto tokens stored, no atproto client secret — the OAuth client is
public (
token_endpoint_auth_method: none) and the access token is discarded after readingsub. - delta-git OAuth issuer (
@cloudflare/workers-oauth-provider): the worker mints its own access/refresh tokens for agents, MCP, and Git Smart HTTP. Tokens are stored SHA-256-hashed inOAUTH_KV— a KV dump yields no usable credentials. Tokenprops(userId/did/namespace) are encrypted so only the token holder can unwrap them. Scopes (repo:read,repo:write,offline_access) are enforced per-surface on top of namespace membership. First-party clients sender-constrain tokens with DPoP (cnf.jktbound at issuance, single-usejtireplay protection in KV); the git transport accepts bound tokens as plain bearer because stock git cannot mint per-request proofs. - Repo secrets are write-only:
PUT /dg/secrets/:nameencrypts withDG_KEKbefore storing in the repo DO; values are never readable back through any API. - PATs are stored SHA-256-hashed; the cleartext is shown once at issue.
- Private repos never appear in federation, XRPC, leaderboards, or exports; visibility checks happen at route resolution.
op_logis hash-chained per repo (prev_hash→hash);/dg/oplogand the/dg/exportbundle carry the chain for independent verification.- Merge attestations are DSSE/in-toto envelopes in R2 (
attestations/<oid>), embedded in the export bundle. /dg/exportproduces a signed provenance bundle (HMAC-SHA256 over the canonical manifest withDG_KEK) — anti-lock-in and post-incident audit.- DO state snapshots (
do/<id>/snapshots/latest.json) enable coordination-state rebuild without trusting a live DO.
Platform jobs (CI runs, security scans, mirror pushes, chaos) and dev
workspaces run untrusted-ish code inside ForgeSandbox containers
(src/worker/compute/).
Isolation. Sandbox ids are derived job-{ns}-{kind}-{id} /
ws-{ns}-{id} through compute/sandbox.ts — the only sanctioned way to
obtain a stub. There is no ambient sandbox registry; cross-space access
requires bypassing the helper. Job sandboxes sleep after 10m idle,
workspaces after 30m.
Auth. Every workspace surface (exec, files, preview) requires owning space membership — previews are never public. Job sandboxes are queue-internal; no HTTP route touches them directly.
Egress. Jobs get an in-container iptables default-DROP policy
(compute/egress.ts) resolved per kind: ci → package registries + VCS
hosts; security-scan → vuln DBs + self; federate → self + target host
only. The policy applies before job-controlled code or secrets stage.
When the container lacks NET_ADMIN the apply fails open — every caller
logs *-egress-not-enforced rather than pretending; the federate path is
the exception and fails closed because it holds the mirror key.
Secrets. User secrets are client-sealed — no code path can hand one to
a container. Platform material (mirror SSH key) is written to disk inside
the sandbox (mode 600), never via argv or env, and deleted in finally
before the job returns. runJob's envVars field is documented
never-secrets.
Residual risks (accepted, documented): DNS stays open inside locked sandboxes (needed for runtime resolution) so DNS-tunnel exfil is theoretically possible; NET_ADMIN-less containers run egress-open; a compromised sandbox has no path to other spaces' data because creds never enter the image.
See docs/ops.md for the ceilings table. Rate limits are KV token buckets;
idea/post/import lanes have the tightest limits since they're the spam surface.
Storage quota is enforced pre-finalize in the receive pipeline.
wrangler deploy is intercepted by a local security gate (gitleaks + trivy +
semgrep). Do not bypass with SECURITY_SKIP=1 except emergencies.