Status: Draft
Type: Standards Track
Category: Identity / Gateway
Created: 2026-08-05
Requires: CIP-6 (actor ownership), CIP-14 (gateway ingress), CIP-15 (routing), CIP-40 (chain identity and signing), CIP-49 (account authorization policy)
Relates-to: CIP-18 (payments), CIP-26 (account libraries), CIP-27 (actor fork), CIP-39 (rooms)
Cowboy Cloud and Cowboy Chain. This CIP specifies the Cowboy protocol. Cowboy Cloud runs on this protocol today. Cowboy Chain, a public network on the same protocol, is under consideration for a future release. Where the two differ, this CIP says so.
1. Background
Actor addresses and website hosts can be discovered through public chain and explorer indexes. An address being difficult to guess is therefore no protection for an actor’s website. An attacker can enumerate new websites and invoke exposed handlers, consume actor-funded work, or change application records before the creator has chosen to share the site. A sign-in screen alone is insufficient. Another signed-in account must not acquire access to the creator’s private site. A lock drawn by actor JavaScript is also insufficient: the request has already reached the application, and actor code can remove the control. Cowboy websites need wallet authentication that remains available when Dashboard or the identity provider is unavailable. Third-party apps, linked Google accounts, and native clients also need a common OIDC identity service. These are distinct authentication paths over the same Cowboy principal; neither creates transaction signatures, spending grants, or authorization to a particular room or resource.2. Goal
- Every actor website starts with Only me access, including sites without a routes table, static sites, and workload sites.
- The owner can view a site after Continue with Cowboy or Continue with Google, when that Google identity is explicitly linked to the owning Cowboy account.
- Continue with Cowboy on a hosted website verifies a wallet proof directly at the gateway. With a working gateway and chain, Dashboard or provider failure does not prevent a new wallet login or invalidate an existing gateway-direct wallet session.
- Private pages load directly in the browser once the gateway admits the signed-in owner. The gateway’s login page shows who is signed in and offers sign-out. Only the owner can change sharing, through a wallet-approved transaction.
- The gateway rejects an unauthorized request before actor execution, storage fetch, payment admission, or actor-funded job dispatch.
- Third-party websites and native apps use the same identity service through OpenID Connect (OIDC).
- Cowchat Mac can authenticate an account and obtain a separately authorized room-discovery grant. Implementing that client is outside this CIP’s implementation slice.
3. Scope
This CIP specifies identity, account linking, site access, private page serving, and relying-client behavior. It does not implement wallet custody, wallet recovery, transaction sponsorship, or a Google-to-wallet key derivation scheme. Google Workspace membership does not itself confer a Cowboy account or room permission. Wagmi and WalletConnect integration is separate work. Their future signers must fit the structured wallet-proof boundary without changing the app-facing OIDC protocol. No arbitrary-hash signing endpoint is introduced here. Access control does not make public chain state or public CBFS objects confidential. Private application data must use private storage and an authenticated serving path (§11). A public index can continue to reveal that an actor exists; it must not provide an unauthenticated path that executes its private website.4. Identity
4.1 Authentication authorities and principal
The gateway wallet authority is the gateway deployment’s configured canonical HTTPS authentication origin. It issues and verifies its own wallet challenges and maintains gateway-local sessions, independently of Dashboard and the OIDC provider. Actor code cannot select this origin. Gateway-direct authentication needs no provider account row, opaque subject, authentication epoch, token, discovery document, or status call. The Cowboy identity provider is the optional operator-configured HTTPS OIDC issuer for linked Google login, third-party OIDC clients, and the native-client profile in §6. Its trusted sign-in page can authenticate the user through a Cowboy wallet or a linked Google identity. It is not the root of the gateway-direct wallet path. Applications never receive wallet keys, Google tokens, or either authority’s authentication cookies. A Cowboy principal is(chain_id, chain_instance_id, account_address). Chain identity is release-pinned and checked according to CIP-40. A numeric chain ID alone is insufficient to distinguish deployments. A relying service MUST verify both the issuer and the complete configured chain identity before accepting a principal.
In the OIDC path, sub is a stable, opaque provider subject. Clients MUST NOT derive a wallet address from it. The provider supplies https://cowboy.inc/claims/account only when the client is authorized to receive it. Gateway-direct sessions instead obtain that principal from the verified wallet proof and configured chain identity, without requiring an OIDC subject. The account claim’s exact JSON shape is:
chain_id is a canonical unsigned decimal u64 string with no leading zeros; it never passes through a JavaScript number. Unknown/missing fields, wrong encodings, and a mismatch with the resource’s configured identity reject the claim. The provider-authenticated gateway path and Cowchat’s resource server require that claim. Direct gateways obtain the same principal from their saved wallet proof instead. General third-party OIDC login can use the opaque subject without requesting the account claim. Neither an email address nor its domain is a Cowboy principal.
4.2 Wallet authentication
Wallet authentication proves control of the selected Cowboy account with a structured, purpose-specific statement. The authenticating authority—the gateway for direct website login, or the provider for an OIDC/link transaction—creates a cryptographically random, single-use challenge of exactly 32 bytes, expiring withinLOGIN_CHALLENGE_TTL_SECS. It binds the challenge to its own login transaction, exact configured issuer, account, complete chain identity, purpose, and expiry. The two authorities have independent one-use stores; a proof issued by one is not evidence of an outstanding challenge at the other.
This CIP defines the login statement independently of transaction signing. The initial profile is EIP-712 secp256k1 account-policy control:
keccak256(0x1901 || domainSeparator || hashStruct(Login)). issuer is the exact configured gateway authentication origin for a direct login, or the configured OIDC issuer for a provider transaction, without URL normalization. transactionId is an independent random 32-byte one-use transaction ID issued by that authority. Times are Unix seconds; issuedAt <= now < expiresAt, with lifetime at most 120 seconds. purpose is exactly login, link_google, unlink_google, or replace_google. For login, binding is the EIP-712 hashStruct of LoginContext(string clientId,string resource,string scope); scope is the sorted, unique, space-separated scope string. The OIDC path uses the exact registered resource or empty for identity-only login. The gateway-direct path uses the fixed context in §5.3.1 and accepts only the login purpose. Linking/unlinking uses the hashStruct of GoogleIdentity(string issuer,string subject) for the verified Google identity. Link replacement binds binding to the hashStruct of GoogleLinkReplacement(string oldIssuer,string oldSubject,string newIssuer,string newSubject) and requires a dedicated replace_google purpose. The wallet displays these bound context fields, not just the hash.
The proof carries the complete CIP-49 AccountSignature: policy_revision and 1..16 strictly ordered, distinct key-indexed signature slots. The authority verifies it against the account’s current CIP-49 policy, read from a fresh finalized-state proof (§5.3.1). Three revisions MUST be equal: the signed policyRevision, the AccountSignature revision, and the current policy revision. The authority saves the current revision when it issues the challenge, so a policy change between challenge and completion makes the signature fail. Only secp256k1 EIP-712 slots without inline public keys are supported. Each slot signature is exactly 65 bytes r || s || v, v in {0,1}, canonical low-S, with lowercase 0x hex encoding in JSON. A high-S slot is rejected. The authority recovers each key over this same login digest. For a stored policy it requires exactly threshold slots, each index inside the key list, each recovered key equal to the key at its index. An authenticated absent AccountAuth row selects AddressDerived at revision zero: exactly one slot, at index 0, whose key derives the account. A stored revision-zero 1-of-1 policy remains a stored policy. A missing, malformed, stale or unavailable policy claim rejects the proof. Revision and roster shape are checked before recovery. Changing proof metadata cannot update the signed revision.
There is no address-only check. A verifier that recovered one key and compared its derived address to the account would keep accepting an account’s original key after the account replaced it under CIP-49.
Both authorities reject unsupported account and signature algorithms. A new algorithm needs a new proof version with shared codec vectors. The statement lives in the shared canonical codec (cowboy-protocol-codec login) and the wallet approval interfaces; CIP-40 does not define it. An authority MUST NOT implement a second recovery path, confuse a signer key with a different account, or accept a transaction signature as a login proof. A wallet reconstructs the statement and displays the authority, target context, and account before approval. A paired CLI receives a typed login operation, never an unexplained digest. Unsupported signing forms fail closed.
The authenticating authority atomically matches and consumes its saved wallet transaction during verification; signature recovery alone does not consume a challenge. Direct authentication produces a gateway-local session; provider authentication produces a provider session. Neither produces a transaction or delegated signing authority. Wallet secrets remain inside the wallet. Existing provider sessions can be reused only on the provider path, subject to client max_age and the fresh-approval requirements below.
4.3 Google linking
Continue with Google uses Google’s OIDC authorization-code flow. The provider validates signature, issuer, audience, expiry, and the one-use login transaction’s state and nonce. It identifies the Google account by the stable(issuer, sub) pair. Email, display name, the email suffix, and the request’s hd hint MUST NOT be used to link accounts automatically. If an operator restricts Google login to a Workspace domain, it MUST validate the returned hd claim against its configured domain policy.
Linking Google to an existing Cowboy principal requires both a fresh Cowboy wallet proof and a fresh Google authentication inside one short-lived, single-use link transaction. The page names the account being linked. The server MUST enforce uniqueness of the Google identity link and reject a link already owned by another account. A first Google sign-in with no link offers Connect your Cowboy account; it does not claim an existing account by email or silently create or merge wallets.
The link stores an authentication mapping, not a wallet private key or a capability to sign transactions. The flow requests only identity scopes (openid, and email or profile if the UI needs them), never Drive, Gmail, or other Google data permissions. Existing Google-backed wallet unlock remains a separate wallet feature.
A linked Google login can authenticate the Cowboy principal for app access. It cannot sign a Cowboy transaction, transfer value, change account ownership, install a mount, or change site sharing. Those operations retain their independent wallet approval. Linking, unlinking, and replacing a link require fresh Cowboy wallet proof. Dashboard link-management endpoints also retain Confirm-tier exact signed-request authorization and idempotency; the contextual Login proof is not a substitute for that request credential. They increment the principal’s authentication epoch and revoke sessions and grants derived from the changed Google link. Revocation propagation follows §5.4. Unlinking cannot require access to the old Google account when the owner still controls the Cowboy account.
4.4 Credential tiers
Both gateway-direct wallet login and OIDC login establish Signed in only. A Cowboy wallet login proof, linked Google authentication, provider session reuse, code exchange, and token refresh MUST NOT establish Unlocked, setstep_up_at, or renew an existing unlock deadline. auth_time describes the original verified authentication, not the latest refresh or API activity. Identity claims and authentication-method labels are not credential-tier grants.
Dashboard routes retain this credential model: Signed in for ordinary account access; Unlocked only after its separately verified step-up, for an absolute maximum of fifteen minutes; Confirm only with a fresh signed proof for the exact request and the required operation/idempotency binding. OIDC identity, access tokens, and a fresh Google login cannot substitute for that request proof. The route manifest remains authoritative. Hosted apps and Cowchat must likewise retain any resource-specific approval tier rather than importing a provider’s session as an unlock.
A passkey assurance claim requires the server-verified WebAuthn ceremony: a one-use server challenge, exact origin and RP ID, account-bound credential public key, and verified user verification. A browser unlocking a locally wrapped wallet key, an amr label supplied by the client, or possession of a wallet signature alone is not evidence of that ceremony. The server records the verified event and its time; it can satisfy a separately requested step-up only under that step-up endpoint’s policy. Login/refresh never copies a client’s assertion of assurance into credential-tier state.
5. Common sign-in protocol
5.1 OIDC surface
The provider MUST support OIDC discovery, a JWKS endpoint, authorization-code flow with PKCES256, a token endpoint, and userinfo. It MUST NOT support implicit or password grants for this feature. It publishes supported claims, scopes, signing algorithms, and endpoints in discovery metadata.
Clients register exact redirect URIs and their allowed resources and scopes. Arbitrary redirect hosts, actor-supplied issuer URLs, and wildcard actor-domain callbacks are forbidden. Client registration grants no access. Dynamic enrollment of actor hosts requires the operator to verify the canonical host binding; until that exists, callbacks are explicitly registered.
An authorization request binds the client, redirect, requested scope and resource, PKCE challenge, OIDC nonce, and browser login transaction. The client validates state. Authorization codes last at most AUTH_CODE_TTL_SECS, work once, and are bound to the exact client, redirect, and PKCE verifier. Exchange and grant creation occur atomically. Codes and tokens MUST NOT be logged.
ID tokens are signed by the provider and carry iss, sub, aud, iat, exp, auth_time, nonce, the provider session identifier sid, and an authentication method (cowboy_wallet or google_linked). A provider accepting several wallet algorithms reports their method without overstating assurance. Relying clients validate signature, allowed algorithm, issuer, audience, expiry, nonce, and required account claims with a maintained OIDC library. Access tokens are resource-bound and are not accepted as ID tokens; ID tokens are not accepted as API bearer tokens. The initial access-token profile is a random opaque bearer token of at least 32 bytes. Resource servers authenticate to the provider’s RFC 7662 introspection endpoint over authenticated TLS. An active response supplies active, client_id, scope, exp, sub, the exact resource aud, and the account claim when authorized. Revoked or unknown tokens return active: false. Resource servers accept only their registered audience and cache successful introspection no longer than §13’s status bound. Discovery metadata publishes the introspection and RFC 7009 revocation endpoints. Browser clients never introspect with a resource-server secret.
The provider’s authorization, login, consent, account-linking/replacement/unlinking, and associated error pages MUST send Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY as response headers. Gateway-direct wallet approval and its error pages have the same requirement. They run only as top-level pages, never inside a client or actor frame. A client cannot register an exception. Browser tests must attempt cross-origin framing and prove that neither wallet approval, identity disclosure consent nor a link approval can execute through the embedded page.
5.2 Grants
openid identifies the user. The optional cowboy:account scope discloses the Cowboy principal after consent. Neither scope creates a Store connection, transaction grant, resource mount, or room membership.
Store MCP retains its separate store grant and resource audience. Common client, code, PKCE, and token mechanics MAY be shared, but an identity-only code exchange MUST NOT create a Store agent connection or inherit Store permissions. Connected-app settings distinguish identity disclosure from resource grants.
Refresh tokens, when issued, are client-bound, resource-bound, and hashed at rest. Every refresh token belongs to one durable rotation family and its authorization grant. A successful refresh atomically consumes the presented token and creates its single successor and access token under that family. The absolute family deadline is the earliest of the consent/grant expiry and REFRESH_ABSOLUTE_TTL_SECS from initial grant issuance. At initial issuance, its idle deadline is issuance time plus REFRESH_IDLE_TTL_SECS, capped by the absolute deadline. Each successful refresh sets the next idle deadline to refresh time plus REFRESH_IDLE_TTL_SECS, again capped by the absolute deadline. Rotation cannot extend the absolute deadline, increase scope, or change client/resource.
Presentation of an already consumed, previously issued refresh token is reuse. In one transaction the provider revokes the entire family and grant, including the active successor and every descendant, and marks dependent access tokens inactive. All resource servers reject dependent access within AUTH_STATUS_MAX_AGE_SECS; local introspection caches cannot extend that bound. No branch of a revoked family may mint another token. Unknown random tokens return invalid_grant without revoking an unrelated family.
Concurrent refresh requests for the same token are serialized at the family/consumption record. At most one can rotate it; the other is reuse and revokes the family, including the winner’s just-issued credentials. The client must serialize refreshes and cannot retry an ambiguous refresh with the old token. A lost response requires a new authorization-code flow. No reuse grace interval silently accepts a consumed token. Expired family, idle deadline, or grant returns invalid_grant and requires fresh authorization. Identity/session reuse during that authorization still does not restore an unlock (§4.4).
Clients receive only scopes the user approved and the resource server supports. A revocation endpoint atomically revokes the grant and its rotation families and stops future exchanges and live API access under the same status bound.
5.3 Hosted websites
Hosted gateways support a direct wallet path and may additionally enable the provider path. The gateway serves its own trusted sign-in UI and wallet challenge/verification endpoints; Continue with Cowboy does not first contact Dashboard or the provider. A provider outage can disable Continue with Google while direct wallet login continues. The wallet approval integration and its required assets MUST remain usable without Dashboard/provider APIs, account records, or hosted UI. The gateway builds and verifies proofs with the shared canonical codec and verifier, and calls no Dashboard endpoint. Both paths establish the host-only__Host-cowboy-session cookie with Secure, HttpOnly, SameSite=Lax, Path=/, and no Domain. Cookie bytes are opaque, random, and stored only as a hash server-side. The server-side record binds the principal, actor, exact canonical host, authentication method, expiry and revocation state, plus an immutable authentication source: GatewayWallet or ProviderOidc. A method label such as cowboy_wallet is not sufficient to distinguish them. A GatewayWallet record binds its local verified proof event, including the public keys that signed the login, and has no provider session/epoch dependency. A ProviderOidc record additionally binds the provider issuer, session and authentication epoch. Clients cannot select or relabel that source. Changing a hostname’s actor binding invalidates either kind of session. Gateways sharing a host use a common gateway-owned challenge/session backend; a failed or unavailable local lookup denies access. The direct path MUST NOT query Dashboard-owned identity/account/session tables or require an existing provider subject. No self-contained cookie format is defined.
/_cowboy/auth/* endpoints are gateway-owned and cannot be shadowed by actor routes or service workers. For either path, server-generated state binds the initiating browser, the selected account once authenticated, the actor, the host and the configured authority. Every successful authentication issues a new session identifier. The gateway rejects completions from another browser or for another account, replays and login CSRF. Query parameters cannot select an actor binding or authority. On the provider path, server state also binds the exact callback and local return path. Return destinations are validated local paths, never network-path references, backslash forms, or absolute URLs, and OIDC callback codes are single-use and removed from browser history by redirecting to a clean URL.
Only top-level GET or HEAD navigations accepting HTML redirect to sign-in. API requests receive 401 with gateway authentication metadata identifying the direct wallet path and, when enabled, the optional OIDC path. A signed-in principal lacking access receives 403. A gateway never redirects or replays an app mutation through login.
5.3.1 Gateway-direct wallet login
The gateway issues its own independent 32-byte challenge and transaction ID. It constructs the §4.2Login statement with issuer equal to the configured canonical gateway authentication origin, the selected account, the configured full chain identity, and purpose = login. Its fixed LoginContext uses clientId = "cowboy-gateway-wallet", scope = "cowboy:account", and resource = <canonical_site_origin> + "/_cowboy/resource/actors/" + <actor_address>, where the address is lowercase 0x hex. The site origin and actor come from the verified host binding, not caller-selected URLs. This context identifies the target site; it is not an OIDC grant, Store permission or room grant. The wallet displays that authority and target before approval.
The gateway stores the exact statement, the account’s current policy revision, a hash of the browser secret, the actor/host binding, expiry and one-use state in its own database. Completion takes the account and target from that saved row; nothing in the completion request can override them. In one database transaction, completion consumes the challenge, verifies a fresh finalized-state proof and the signature against the account’s current policy (§4.2), records exactly which public keys signed, and creates the session. Any failure rolls the whole transaction back, so a failed completion leaves the challenge unused and creates no session. Challenges and sessions are replay-protected across all gateway replicas serving that host, which share one gateway-owned database.
Issuer and site origins
The trusted sign-in page runs at the configured issuer origin, at/_cowboy/login?site=<site origin>, with its assets under /_cowboy/auth-ui/. The site query can only select a configured site, and the page may connect only to that site. The issuer origin MUST differ from every site origin the gateway serves; the gateway refuses a configuration where they are equal. The issuer and the sites it signs into MUST share one registrable domain, so the browser treats their requests as same-site. For example, the issuer can be https://login.sites.example while sites are https://alice.sites.example and https://bob.sites.example. That parent domain must not be on the Public Suffix List, because the browser would then treat each site as its own registrable domain. The gateway catches a wrong layout per request (status 412 below). Each site origin here is the app (payload) origin of §8, and the session cookie is host-only on it. The sign-in page calls the site host’s /_cowboy/auth/* endpoints with credentialed requests, and the site host sets its own host-only cookies in the responses. No code, token or credential passes through a URL, and there is no separate exchange step.
Two cookies are used. Both are host-only on the site host, with
Path=/, Secure, HttpOnly, SameSite=Lax and no Domain:
- the flow cookie
__Host-cowboy-login-<transactionId hex>holds a random browser secret for one sign-in attempt and lasts 180 seconds; the database stores only its hash, and completion requires it; - the session cookie
__Host-cowboy-sessionholds the random session token; the database stores only its hash, and itsMax-AgeisAUTH_SESSION_TTL_SECS.
- At most one
Hostheader, naming a configured site. An unknown host gets404. - Exactly one
Originheader, equal to the issuer origin. Anything else gets403. - Exactly one
Sec-Fetch-Siteheader with the valuesame-siteorsame-origin. A missing or duplicated header gets403. Across-siteornonevalue gets412with a setup-required message, because the issuer and site are not laid out on one registrable domain.
Vary: Origin and Cache-Control: no-store, reject JSON bodies with unknown fields, and limit bodies to 16 KiB. Malformed input, including a malformed AccountSignature, returns 400. An invalid, expired or consumed credential, a failed signature check and a session whose signing key was removed return 401. A proof that cannot be fetched or verified, including a stale proof or a missing claim, returns 503, as does an unavailable database.
Finalized-state proof
The gateway learns the account’s policy from one operator-configured RPC source, through a finalized-state proof. The operator provisions a trusted checkpoint and that RPC URL. The chain ID and chain instance used in the login domain come from the checkpoint. Actor code and requests cannot select the source or the trust anchor. The gateway requests a proof with two claims: the account’sAccountAuth row and the target actor’s ActorOwner record (§7.1.1). The RPC requires at least one actor claim, and the ActorOwner claim also shows that the target actor exists with protocol ownership. It accepts the proof only when all of these hold:
- the proof verifies against the trusted checkpoint through the validator set’s finality signatures;
- it contains exactly the requested claims; an omitted account claim fails closed, and an authenticated absence of
AccountAuthselectsAddressDerived; - the
ActorOwnerrecord is present and names the target actor; - the finalized block’s timestamp is at most
LOGIN_PROOF_MAX_AGE_SECS(60 s) old and at mostLOGIN_PROOF_CLOCK_SKEW_SECS(3 s) ahead of the gateway database clock.
ActorOwner claim shows who owns the actor on chain. It does not establish which host serves the actor or who may view the site; host binding and site admission are separate checks (§7, §9).
Admission after login uses the full wallet principal and current on-chain site/route policy. Dashboard/provider discovery, account existence, opaque subjects, Google-link epochs, provider AUTH_STATUS and provider logout are not prerequisites. Provider or Dashboard failure never converts a wallet session to anonymous access and never disables direct login; failure to verify the chain, host binding, local one-use state or local session still fails closed. A provider-authenticated session cannot be converted to GatewayWallet by relabeling or by outage fallback: conversion requires a new gateway-issued challenge and fresh wallet approval.
5.3.2 Optional provider login
When enabled, the gateway is also an OIDC relying client configured with its trusted provider issuer and allowed chain identity. This path redirects through the provider and returns to an exactly registered gateway callback. The gateway validates the code, issuer, audience, full chain, PKCES256, state and nonce with a maintained OIDC client library, then creates a ProviderOidc session. The saved callback transaction contains the PKCE verifier and nonce; a callback cannot establish an unsolicited browser session. Google-linked login and provider wallet login follow provider status/revocation rules. If this path is unavailable, it fails closed for its own dependent credentials and offers the independent direct wallet path; it does not silently grant access or reuse provider assertions as local wallet proofs.
5.4 Expiry and logout
Provider and gateway sessions have idle and absolute expiry. At session creation timet, the server records absolute_deadline = t + AUTH_SESSION_TTL_SECS and idle_deadline = min(absolute_deadline, t + AUTH_SESSION_IDLE_SECS). A session is valid only while server time is strictly before both deadlines. Every accepted request resets its idle deadline to min(absolute_deadline, now + AUTH_SESSION_IDLE_SECS); it never extends the absolute deadline. This includes background page polling, automatic asset/subresource requests, token refresh, status/keepalive requests, open streams and their periodic authorization checks, and stream messages. Background requests therefore keep an idle session alive while the absolute deadline still bounds its lifetime. Expiry checks precede renewal. The same maxima apply to provider browser sessions; provider token-family idle expiry is separately defined in §5.2. Session activity never establishes or extends Unlocked or Confirm (§4.4). Authorization is re-evaluated against current site policy on every request; a login does not freeze permission.
For GatewayWallet, expiry and logout/revocation come from gateway-local state, and continued authorization comes from current on-chain ownership and site/route policy. Local logout clears and revokes the local session, including its dependent streams. These sessions MUST NOT check provider AUTH_STATUS, require a provider authentication epoch, or depend on provider back-channel logout. Provider failure, provider logout or Google-link changes do not revoke a directly verified wallet session. Changes to on-chain ownership, host binding, site policy or the account’s keys, and gateway-local revocation, apply regardless of provider availability. Dashboard/provider sign-out and account-deletion responses must describe their own scope and MUST NOT claim to revoke independent gateway-direct sessions; those end through gateway-local logout, sign out everywhere, removal of a signing key, or expiry. A site-policy change affects admission of each request and leaves the session in place.
A GatewayWallet session records the public keys that signed its login. Each session stores the finalized block time of the last proof that checked its keys. When LOGIN_PROOF_MAX_AGE_SECS (60 s) have passed since that block time, the next request fetches a fresh finalized-state proof (§5.3.1) before it is admitted. Between rechecks, requests are admitted without an RPC call. The session survives these changes:
- adding a key to the account’s policy;
- raising or lowering the threshold;
- any other change that advances the policy revision but keeps every key that signed the session.
issuedAt. A new wallet approval can establish a new session. The scope is every site and replica that shares the gateway database. A gateway with a separate database keeps its own sign-out records. Sign-out grants no access and changes no on-chain authority. Changing which keys may sign for the account is CIP-49’s SetAuthPolicy; removing a key ends that key’s sessions as described above.
For ProviderOidc sessions and provider-issued resource grants, the provider exposes authenticated session/grant status and OIDC back-channel logout. Gateways and resource servers check that status at least once per AUTH_STATUS_MAX_AGE_SECS and consume back-channel revocations. A stale or failed provider status lookup fails closed for those credentials after that bound. Provider logout, account unlink, link replacement or grant revocation stops only dependent provider sessions and resource grants within that bound and closes their streams. A third-party app’s own offline session is not guaranteed to disappear immediately; its documented lifetime and logout integration determine that behavior. Neither path freezes site permission or establishes Unlocked/Confirm.
6. Native clients and Cowchat
A native client uses the system browser and authorization-code flow with PKCES256, state, and nonce. It is a public client and embeds no client secret. Redirects use claimed HTTPS app links or a registered loopback interface with the native-app port exception. Custom schemes require an explicit registration and interception threat review; they are not enabled by a generic redirect-scheme deny-list. Tokens never appear in an application deep link, command line, or log. Native tokens live in the OS secure credential store, not a room config or plaintext preferences file.
Cowchat Mac is a supported client of this profile. Its release configuration pins the trusted issuer, complete chain identity, and Cowchat API resource URL. The service publishes RFC 9728 protected-resource metadata at that configured origin; discovery must match the pinned issuer and exact resource, and a room-supplied URL cannot redirect token exchange or select a new audience. The identity service must permit its registered client to request openid, cowboy:account, and the separate cowchat.rooms.read scope for the configured Cowchat resource audience. This read grant lets the Cowchat service return rooms the principal is authorized to discover. It grants no Store access, wallet signing, or right to join or send messages to a room.
The Cowchat service, not the UI, verifies the access token’s audience, principal, scope, live grant, and room authorization. It returns only authorized room descriptors for that deployment. Public discoverability, joined membership, owner status, and private-room grants remain distinct under the room service’s authority model. Knowing a room UUID, owning an unrelated actor, or sharing a Google Workspace domain grants nothing. History, attachments, subscriptions, joining, and sending each retain their resource checks. A room authorization revocation removes the room from discovery and terminates unauthorized live subscriptions within the service’s documented authorization-refresh bound, which MUST NOT exceed AUTH_STATUS_MAX_AGE_SECS for this integration.
The Mac app refreshes credentials through the provider, clears them on sign-out, and clears account-specific room state on account or chain switches. Login does not recover encrypted room keys or disclose rooms whose separate key/membership requirements are unmet. This CIP specifies support and acceptance criteria; no Mac UI implementation is required by the specification change itself.
7. Website access
7.1 Site policy
The website owner is the account in the protocol-ownedActorOwner record defined below. Application ownership keys, including __OWNER__, do not grant website administration. Every Owner access rule and every sharing change use the protocol actor owner.
Every actor has a protocol-owned web-access record:
Owner at revision zero. This default applies at actor creation, including generated templates, undeclared HTTP fallback, static operator workload bindings, custom domains, and aliases. A record is bound to the complete chain identity and actor; a hostname cannot override it. Forked actors start with an Owner site policy even when the source had site publication. Forks and transfers never inherit site publication; Public routes declared in the route table stay public until the owner changes them. Each accepted update records the current owner as owner_at_update; an ownership change invalidates prior publication and resolves to Owner until the new owner explicitly publishes. No owner, malformed owner, or zero owner admits no one under Owner.
A new owner-authorized protocol operation SetWebAccess(actor, expected_revision, expected_routes_digest, visibility) updates the record with compare-and-set revision semantics. The chain verifies a direct transaction signed by the current website owner under the activated transaction-signing rules. Actor/deferred execution, delegated application authority, a gateway assertion or a linked Google identity cannot satisfy that signature. For Public, expected_routes_digest is required and must equal the finalized current routes digest, which is stored as published_routes_digest. For Owner, that argument must be null: locking depends only on the policy revision and current owner, so a concurrent route edit cannot prevent locking. Locking clears published_routes_digest. The operation uses normal CIP-3 transaction charging; publication does not add a spending grant. It atomically stores the current owner and increments the revision, or rejects the operation. The canonical protocol codec, transaction approval inspection, SDK, and CLI MUST cover this operation before the first host exposure. Storage is in the authenticated protocol metadata alongside actor route installation, not actor-writable application KV. Actor code, deployment generators, gateway sessions, and Google identity links cannot publish a site on the owner’s behalf. A deployment cannot install Public implicitly: it requires the same explicit wallet approval with the publication fact visible to the owner.
Owner admits only the current owner principal for Site-gated routes. An explicitly Public route has the exception specified in §7.2. Public opens the site gate; individual routes can remain more restrictive. Anyone describes the site gate, not permission to invoke every application action. Public sharing never removes an application or runner’s transaction, role, payment, or mount checks.
7.1.1 Actor ownership
Owner at access revision zero. Genesis/system deployments require an explicit owner in their release configuration. Creation must establish a valid nonzero owner or fail; it cannot publish a reachable actor or website binding without ownership metadata. Absence or malformed ownership metadata grants no claim or first-writer initialization path.
Only TransferActorOwnership(actor, expected_owner_revision, expected_access_revision, new_owner) changes protocol actor ownership. It requires a direct transaction signed by the current protocol actor owner, both current revisions and a new owner at or above RESERVED_SYSTEM_ADDRESS_LIMIT (0x100). The zero address and the reserved system band [0x00, 0x100) cannot receive actor ownership by transfer, because no key signs for them: the actor could never be transferred again. The transaction decoder, execution and canonical wallet inspection all reject such a transfer. In one transaction it increments the ownership revision, replaces the owner, and writes Owner access with an incremented access revision and no publication digest. It does not read or validate the route table. A pending old publication approval cannot survive transfer, including transfer away and back to the same account. Canonical wallet inspection displays the exact actor, both expected revisions, the new owner and the resulting private mode before signing. Application-level ownership changes do not perform this operation implicitly.
Website policy reads the single authoritative ActorOwner rather than maintaining a second website owner. Every TransferActorOwnership atomically resets website access to Owner and advances its access revision, including for actors without a current website binding, so a later binding cannot revive an old publication. The owner grants no implicit powers: each protocol feature must explicitly specify whether and how it authorizes that owner. Application __OWNER__ is ordinary application data that the protocol never trusts for ownership or authorization.
ActorOwner.owner is an account address, and CIP-49 lets an account replace its keys without changing that address. If the owning account loses or leaks its key, it changes its policy with SetAuthPolicy or regains control through its CIP-49 recovery account. The new policy then signs TransferActorOwnership and SetWebAccess with no change to the ActorOwner record. An account that has lost every key and has no recovery account cannot transfer ownership or change sharing. This CIP adds no other recovery path, first-writer claim, Google-account override or gateway-admin transfer. The SDK APIs, examples and documentation use ActorOwner and the explicit TransferActorOwnership operation with its canonical wallet approval facts; writing __OWNER__ does not transfer protocol ownership. Applications may retain that key for their own application-level semantics, clearly distinguished from protocol ownership.
Ownership and access metadata are protected against every actor write, delete, checkpoint import, storage replacement and fork-copy path. Gateways verify both records and routes under the same current finalized anchor and complete configured chain identity. A missing or unverifiable owner fails closed; an actor response, host alias, application KV field or deployment generator cannot override it. These ownership operations and creation/fork rules are launch prerequisites alongside SetWebAccess.
7.2 Route policy
The gateway selects a route with the same most-specific matcher and route table used for dispatch. The route table, site policy and protected actor owner MUST come from one verified admission snapshot. A stale dispatch cache MUST NOT select another route after admission. The route-table schema is version 2 and requires anaccess field encoded as a plain string:
"site"; every version 2 wire route encodes a value. Site applies the site’s effective WebAccess: a new site’s ordinary routes remain owner-only, and publication opens them in one action. Owner admits only the current protocol actor owner. Public admits anonymous visitors for that selected route even when the site’s effective policy is Owner. Public routes support landing pages, static assets and webhook requests, including POST. They retain the target’s transaction, role, payment and mount checks. A native RunnerRoute target MUST use Site; the HTTP gateway does not enforce its route access.
A Public route opens only its own dispatch surface. Reserved discovery, OpenAPI, quote, MCP and other gateway projections retain their site checks. When the enabled dispatch table contains any Owner route, actor-scoped projections also require the current protocol actor owner, even when the site is Public. Declaring a Public route does not open those projections. When an explicit reserved endpoint names an actor, an invalid or encoded actor identifier MUST return 404 before an extractor can reinterpret it. Production route consumers MUST require the verified request-bound table; only explicit local mock mode may load a table without admission.
Route validation and gateway admission MUST refuse overlapping lower-access and higher-access volume read routes on the same volume unless the lower-access path is strictly nested under the protected path by a literal prefix. This applies at every priority. For example, Public /admin/public/* may be nested under Owner /admin/*; Public /{section}/* and Public or Site /{a}/{b} cannot overlap that Owner path. Installation MUST repeat this validation after resolving volume IDs. Disabled Owner and Site volume routes MUST retain their path and object protections; disabling dispatch MUST NOT make those objects public. Captured parameter values and wildcard tails do not add matcher specificity. Priority and parameter lengths cannot grant permission to override object privacy.
Volume dispatch MUST reject aliases that lower access to the same object. Trailing empty segments, implicit index.html names, mount prefixes and SPA fallback paths cannot expose an Owner object through a Site or Public route, or a Site object through a Public route. The gateway MUST check the actual object key and its protected route aliases before serving normal or fallback content; choosing a less restrictive URL alone grants no access.
The launcher accepts only the final version 2 route schema. It rejects missing access fields and other schema versions; there is no automatic public fallback. The publication preview lists the enabled routes that will become reachable. No routes are silently added or re-enabled by publication. An actor with no routes table uses Site CIP-14 fallback: owner-only by default, public only after publication approval explicitly naming the wildcard http.request surface and the method set defined below. An invalid table or unsupported version returns 503, without target dispatch; it is never reinterpreted as public or sent through a less restrictive fallback.
7.3 Freshness
The gateway verifies owner, site policy, route policy, and host binding against finalized state. Publication approval binds the current finalized route-table digest as well asexpected_revision; the operation rejects if the enabled surface changed after the owner reviewed it. The digest is keccak256 of the installed canonical route bytes, or keccak256(utf8("cowboy.web.routes.absent.v1")) for an absent table. If the live digest differs from published_routes_digest, effective site access becomes Owner until the owner approves publication of the new surface; clients show the site as Only me — publication needs approval. Locking is not blocked by a route-digest mismatch. The gateway admits requests from a per-site admission snapshot: the site’s ActorOwner, WebAccess and route-table claims from one finalized-state proof, verified against the trusted checkpoint (§5.3.1). The gateway may reuse a snapshot for up to LOGIN_PROOF_MAX_AGE_SECS (60 s) after its finalized block time, so not every request needs a proof. An older snapshot cannot admit a request. Failure to establish current policy returns 503; a stale public cache entry cannot admit a request. Content caches are downstream of this check and cannot answer before access evaluation.
A sharing change remains Pending until its transaction finalizes and the serving gateway has applied the resulting policy. Clients report success only then. Open streams, including SSE connections and WebSockets, re-check their required session (§5.4), site and selected-route access about every ACCESS_REFRESH_SECS (60 s), the same interval as the session key check. A recheck cannot substitute a different selected route into an already open stream. A stream closes within about a minute once a required site, owner or session check fails. Locking, ownership transfer and forks reset site publication; they do not withdraw explicit Public declarations or by themselves close anonymous Public streams. Already delivered bytes cannot be recalled. A new protected request whose Site or Owner check fails is rejected before reaching its target; explicitly Public routes continue to admit anonymous requests.
Operator-only workload routes are always Owner, even when the site record is Public. They cannot become public through an absent-table digest: publication requires moving the route and exact target binding into the canonical owner-approved protocol route manifest, then approving its digest. A gateway with local/static routes that differ from that manifest must reject those routes, not add them to the public surface. For no-table fallback the approved wildcard surface is the CIP-14 http.request handler for GET, HEAD, POST, PUT, PATCH, and DELETE; preflight invokes no handler. Adding an operator route or a different fallback method does not inherit that approval.
8. Private site pages
A private site’s protected pages load directly, as ordinary top-level pages on the site origin. A cookie-authenticated top-level navigation to a Site- or Owner-gated page is admitted after access evaluation (§7.3, §9): the gateway checks the session and the applicable access rule, then serves the actor’s response as the page. A protected navigation without a session redirects to the gateway’s login page (§5.3); a protected API request gets401. Explicitly Public pages and assets remain available anonymously on a private site.
The session cookie may be used only on a top-level navigation, a same-origin request, or a request whose Origin is exactly the site (§9). Every response from a private site carries Content-Security-Policy: frame-ancestors 'none', so no other page can frame it.
After a successful login, Continue on the login page goes straight to the site. The login page is also where the signed-in account is shown and where the user signs out or signs out everywhere (§5.4). Actor pages carry no gateway navigation bar.
Actor code runs on its own site origin and controls its own pages. It cannot read the HttpOnly session cookie, set or clear reserved cookies (§9), or change who may reach the site: that takes the owner’s wallet-approved SetWebAccess. Google-only authentication can view an already linked owner’s site; changing sharing asks for Cowboy wallet approval. The approval for publishing explains that anyone who discovers the URL can reach the site. The approval for locking explains that previously public bytes cannot be withdrawn from a visitor’s copy. Clients show pending, rejected and failed states without saying the site is locked or public before it is.
A custom domain or alias must be on the issuer’s registrable domain (§5.3.1); otherwise the authentication endpoints answer 412 with a setup-required message and no private content loads. No parent-domain cookie is issued. The gateway reserves /_cowboy/auth/* ahead of actor routes and keeps actor service-worker scope from covering it. Operators MUST NOT disable browser cookie protections or put bearer tokens in URLs.
A trusted shell that frames sites under a Cowboy navigation bar is possible future work.
9. Enforcement
The gateway performs these steps before target execution:- Handle reserved authentication, health, and protocol endpoints. Reserved data-bearing endpoints are not automatically public (§10).
- Resolve the exact host/actor binding and current proof-backed policies.
- Resolve the route, or the
Siteno-table fallback under the owner-only default. Answer CORS preflights without invoking the target. - Read any principal from the gateway session or a validated resource-specific access token. Verify its immutable authentication source, full chain, target binding, expiry and applicable live state.
GatewayWalletuses gateway-local session/revocation and current chain policy, without a provider status check.ProviderOidcand provider-issued tokens additionally require verified issuer/audience and live provider session/grant status. An explicitly Public route does not require a principal. - Apply the selected route’s Site, Owner or Public rule (§7.2), including the explicit Public exception to a private site gate. Reject callers who fail the applicable rule without fetching application content, running a handler, reserving actor funds, or submitting a job.
- Apply origin/CSRF, payment, independent account signatures, and target-specific checks. Dispatch only after all required checks pass.
SameSite=Lax lets the browser attach the owner’s session cookie to a request that another actor’s page starts. Sibling actor origins are therefore untrusted. A request carrying the session cookie may use it only in these cases:
- its
Originheader is exactly the site origin; - it has no
Origin, is aGETorHEADwithout an upgrade, and is a top-level document navigation (Sec-Fetch-Mode: navigate,Sec-Fetch-Dest: document), or a same-origin request (Sec-Fetch-Site: same-origin).
Origin. A duplicated Origin, Sec-Fetch-* or Referer header fails this cookie-use check. A request that fails it gets 403 before the session is read. Anonymous Public webhooks do not require a session cookie or browser Origin; their application checks still apply. Wildcard CORS grants nothing. GET and HEAD MUST NOT change application state.
HttpRequestEnvelope, RouteContext, and WorkloadRouteContext carry authenticated_sender: bytes20 | null together with the configured chain context. The gateway sets it only from its verified principal. Receivers honor it only on an authenticated connection from a configured trusted gateway. All incoming x-cowboy-* headers and __Host-cowboy-* cookies (including __Host-cowboy-session and every __Host-cowboy-login-* flow cookie) are stripped before actor dispatch; the gateway re-adds only its own verified context. Actor responses cannot set reserved cookies or override shell framing policy.
Gateway identity authorizes application access only. Value transfers, ownership, site sharing, and mount authority retain their independent signed authorization. A malicious gateway can lie about a principal; this CIP does not replace consensus signatures with a gateway or identity-provider assertion.
10. Bypasses and receipts
Every hostname, operator static route, API route, asset route, HTTP method, WebSocket, and SSE path to the website applies the same verified admission policy and its applicable route rule. An explicitly Public route admits anonymous requests even on a private site. A stale table, cached asset, alias, fallback or operator workload binding cannot manufacture that exception or bypass an applicable Site or Owner check. Runners and workloads reject direct unauthenticated access to protected website surfaces. They require authenticated gateway transport and trusted context, or independently apply the same selected-route rule with its required principal and current policy. A client-supplied context header is not evidence. Direct node read handlers do not manufacture an authenticated principal from a request envelope; an attacker-controlledauthenticated_sender field or query argument confers no authority. Private app state must not be exposed by an alternative public query handler. Arbitrary signed actor transactions remain outside HTTP login and retain their actor and protocol authorization checks.
Async receipts bind the requesting principal, actor, complete chain identity, site-policy revision, and request identifier. Receipt polling and streaming re-evaluate current access; an unguessable request ID alone does not authorize a private response. An authenticated response is Cache-Control: private, no-store; authentication redirects, errors, login pages, callbacks, and private assets are no-store. Shared caches may hold explicitly public responses only and must still check current policy before serving them.
The public explorer/indexer MUST NOT trigger application handlers or produce unauthenticated private-page previews while indexing. It may expose public actor metadata and a generic locked-page description. Requests to protected surfaces receive the same rejection as any other anonymous request; explicitly Public surfaces retain their declared access. Removing a URL from an index is not an access-control mechanism.
11. Confidentiality and abuse
Public chain state, public read-handler output, andVisibility::Public CBFS objects remain publicly readable outside the gateway. A lock prevents unauthorized website access and invocation; it cannot make those bytes secret. Private documents and account data belong in private sealed storage behind an authenticated runner/workload. A deployment MUST NOT advertise a page as confidential while its data is available through a public storage or node endpoint.
Admission enforcement precedes all actor-funded work, including dynamic previews and model calls. Rate limiting is operator infrastructure and is outside this protocol; the gateway itself applies no request rate limits. The load balancer or web application firewall in front of the gateway limits requests per client address. That limit MUST cover /_cowboy/auth/begin, because an unauthenticated begin makes the gateway fetch a proof of up to 4 MiB and write a challenge row. Signed-in sessions and open streams also cause one proof fetch about every 60 seconds each. Each new login removes up to 64 expired sessions and 64 expired challenges that no session references; there is no cleanup daemon. Public sites still need operator abuse limits and explicit budgets. An authenticated owner session is never a spending grant. Failed private requests cannot charge the actor merely because its URL was enumerated.
12. Launch contract
There is one pre-launch website contract: the final route schema, protocol-owned owner-only site gate, independent gateway wallet authority, node admission, SDK/CLI approval facts, and runner/workload enforcement are prerequisites for the first host exposure. Dashboard and the OIDC provider are not launch or runtime prerequisites for that wallet path. Provider/OIDC/Google features require their own complete configured and verified implementation before those paths are enabled. A new deployment or fork is owner-only from its first observable binding. Provisioning must not publish a reachable host while the website prerequisites are missing, and a failed setup serves no application traffic. All gateways serving a host enforce this same contract. Missing, invalid, stale or unavailable chain/policy or gateway-local identity state fails closed. Provider state failure fails closed for provider-derived credentials only; it cannot disable the direct wallet path or promote a failed provider session to a wallet session. No anonymous provisioning window, ungated fallback target, or optional privacy switch exists. The canonical gateway authentication origin, optional OIDC issuer/client registrations/signing keys, and complete chain identity are deployment configuration; an actor cannot select a trust-on-first-use authority.13. Parameters
Operators may shorten these lifetimes, not extend their stated maxima. IDs, secrets, and tokens are bounded in size before parsing. Issuer signing keys have published key IDs, overlapping rotation, and a tested unknown-key refresh/failure path through standard JWKS handling.
14. Acceptance
Conformance requires executable coverage of these scenarios:- A newly deployed site is discovered through the explorer before its owner first visits. Anonymous and unrelated signed-in callers cannot read private application content, invoke a handler, submit jobs, or charge the actor. No-table, rejected non-final schemas, static, workload, custom-domain, deep-link, HEAD, POST, WS, SSE, asset, and receipt paths are covered.
- The owner enters through Cowboy or an explicitly linked Google account and sees the same principal on the login page. An unrelated Google account, matching email, reassigned Workspace email,
hdhint, or same-domain employee cannot become the owner. Link replay, linking to another account, unlink revocation, and account switching fail safely. - With chain and gateway services healthy, disable Dashboard, the OIDC provider and access to their account/session/status APIs. A new direct wallet login still succeeds; an existing
GatewayWalletsession and its pages and subresources continue beyondAUTH_STATUS_MAX_AGE_SECS. No provider subject/account row or discovery/JWKS/status call is made. Provider-derived sessions fail closed at their status bound and a fresh direct wallet proof can establish an independent local session. Re-enabling the provider cannot relabel, replace or revoke that local source. Chain-policy/ownership changes, local logout/revocation, expiry and an unavailable gateway-local store still deny access or close streams as required. - Direct wallet vectors bind the exact configured gateway authentication origin, complete chain, selected account, actor/site resource and fixed client/scope context. A provider proof, link-management purpose, wrong host/actor, stale target binding, replayed challenge, missing or foreign flow cookie, wrong
Origin, cross-siteSec-Fetch-Site, issuer equal to a site origin, or client-selected authentication-source label cannot establish a direct session. Browser tests cover cross-tab completion, login CSRF, session fixation, the cookie attributes and clickjacking refusal; multi-gateway tests prove at most one completion per challenge. Every issued statement fits its complete verification envelope before persistence. - Login vectors verify against the account’s current CIP-49 policy: exactly-threshold 2-of-3 accepts and one or three slots reject; a high-S slot, an out-of-range or descending index, and a signature at a stale policy revision reject; an account’s original address-derived key rejects after the account replaces it. A policy change between challenge and completion makes completion fail.
- Proof tests cover a proof older than 60 seconds, a proof more than 3 seconds in the future, a proof missing the account or
ActorOwnerclaim, anActorOwnerfor another actor, and a proof that fails checkpoint verification; each rejects login and session recheck. - Key-change tests: adding a key and changing the threshold keep every session; removing a key ends exactly the sessions it signed on the first request after the recheck interval, while sessions signed by other keys continue. When the RPC is unavailable at a due recheck, the request is denied and idle time does not advance.
- Sign out everywhere succeeds with the RPC unavailable, ends every session for the account on every replica sharing the database, and refuses challenges and completions begun before it. A fresh wallet approval afterwards establishes a new session.
- Session tests cover the initial idle deadline, accepted foreground and background requests renewing idle time only up to the fixed absolute deadline, and rejection at either deadline before renewal. Tests include page polling, assets, refresh/status/keepalive requests, open streams and their periodic authorization checks keeping an idle session alive. Dependent streams close when the session expires; session activity never renews native unlock or Confirm.
- Actor code can change or forge its application
__OWNER__value without changing the protocol actor owner or gaining publication authority. Direct and actor/deferred deployment, genesis configuration and forks establish the specified owner; forks never inherit site publication. Explicit Public routes remain public until the owner changes them. Ownership transfer requires the current owner’s direct signature and both revisions, clears publication atomically, and rejects stale approval replay even after ownership returns to the original account. Actor writes, deletes and checkpoint/fork-copy attempts cannot forge ownership/access metadata. - The owner explicitly publishes an unmodified generated template and a no-table site; an anonymous visitor can load their page and assets, while explicitly owner-only API routes stay private. Route changes invalidate previous public approval until re-approved. Then locking rejects new Site-gated requests after finalization; explicitly Public routes retain their declared access. Old public cache entries, alternate gateways, direct backends, open streams, and captured receipt URLs do not bypass the lock. Stale or unavailable policy and applicable source-specific status reads fail closed.
- Actor HTML, scripts, service workers, forged context headers and sibling actor CSRF cannot read the session cookie, set reserved cookies or change site access. Private-site responses carry
frame-ancestors 'none', and cross-origin framing attempts fail. Real-browser tests cover session cookies, the redirect to sign-in and back through Continue, and subresources, including Safari. A custom domain outside the issuer’s registrable domain gets only the setup-required response. - A third-party OIDC client signs in without receiving Store, spending, room access, or an Unlocked/Confirm credential. Reused login, linked Google, code exchange, refresh, and session activity never create or extend unlock; passkey assurance requires a verified ceremony and Confirm retains its exact signed-request check. Authorization, consent, login, and linking pages reject cross-origin framing in real browser tests. Parallel refreshes and reuse revoke the whole family/grant and invalidate its access tokens within the status bound; absolute/idle expiry, the first refresh after the initial idle deadline, and lost-response reauthorization are covered. Wrong issuer, audience, account chain, nonce, redirect, PKCE verifier, expired token, replayed code, token confusion, and signing-key rotation have positive and negative vectors. The implementation uses existing reviewed OIDC libraries rather than bespoke token validation.
- A native test client using Cowchat’s registered profile signs in through the system browser and obtains only its room-discovery resource grant. The service returns authorized rooms, rejects another account’s/private-room probes, and revokes discovery and live subscription access. Sign-out and account/chain switching remove local room state. This can be exercised with a protocol test client before Mac UI work lands.
- On a private site, anonymous GET of a declared Public asset succeeds, and anonymous POST to a Public webhook reaches the actor. Site and Owner routes remain protected. A Public wildcard cannot expose a protected object through a trailing slash, an index name, a mount alias or an SPA fallback. Reserved projections remain protected, and percent-encoded explicit actor identifiers cannot bypass admission.
- Protocol vectors encode the final route schema and reject missing access fields and unsupported versions before first host exposure. They bind owner-approved sharing revisions. Actor code, a Google session, and a stale owner cannot publish. Ownership transfer invalidates the previous publication. An operator-only static route cannot become public through an absent-table approval. Concurrent lock/public updates reject stale revisions rather than overwrite one another.