suo

Security

Keys, scoped tokens, data isolation and crawler behavior.

Keys

Three key types, prefixed so a key's privilege is visible at a glance:

PrefixTypeScope
suo_admin_adminFull account access, including settings and key management. Treat as a secret.
suo_write_writeIngest only — write and delete records. Cannot search. Keep on your server.
suo_search_searchPublic. Search only, restricted to specific indexes and (optionally) an HTTP referrer allowlist, and rate-limited. Safe to ship in client-side code.

Every key is suo_<type>_ followed by 32 base62 characters. See Keys for the create, list and revoke routes.

Keys are shown once

A key's full value is displayed exactly once, at creation, with a clear warning. After that, the dashboard shows only its prefix and last four characters (suo_search_…7f3a) — enough to identify it, never enough to reconstruct it. If you lose a key, revoke it and create a new one; there is no way to reveal an existing key's value again.

Lookup and secrets

A key is looked up by the SHA-256 hash of its value, not the value itself — the raw key is never stored. Creating a search key also returns a signing secret, derived with HKDF from the account's master secret and the key's id, exactly once, alongside the raw key. It is not itself persisted — the API re-derives the same value from the master secret whenever it verifies a scoped token — but unlike everything else about the key, it is handed to you so your own server can sign scoped tokens without ever calling suo. Revoking a key takes effect within 60 seconds everywhere, because verification reads through a short-lived cache in front of the durable store.

Scoped tokens

A scoped token is an HMAC-signed claim set — which indexes, which single partition, which extra filters, an expiry, an optional rate limit — minted on your own server with the signing secret from a search key, never a key that reaches the browser. See Scoped tokens for private content for the full claims shape, the signing secret and how to mint one. Because the partition is a signed claim rather than a client-supplied parameter, a bug in your front end cannot leak one user's results to another.

Data isolation

Partitioned indexes give each key (typically a user ID) its own isolated slice of an index — see Partitions. Deleting a partition removes every record and tombstone it holds in a single call, with no 30-day retention, for account-deletion and erasure requests.

Rate limits

Every key enforces whatever ratePerMinute is configured on it, plus a coarse global cap per key and per IP. Domain verification is limited to 5 attempts per domain per minute. All of these are checked and incremented as one atomic step, so a burst of concurrent requests in the same window can't all slip through a gap between a check and the write that records it.

Domain verification and DNS rebinding

Verifying a domain (DNS TXT record or meta tag) and crawling it both resolve the domain first and reject a private, loopback or link-local address before connecting. The same check runs again before every redirect hop, so a page that starts on a public address can't hand the crawler off to one that isn't — see Verify your domain.

That check and the connection it guards are two separate DNS lookups: one over DNS-over-HTTPS to inspect the answer, and a second one made independently when the underlying request actually connects. Cloudflare Workers has no way to pin an outbound request to a specific IP address for a domain outside suo's own zone, so a narrow window sits between the two lookups. A domain owner running authoritative DNS with a near-zero TTL could, in principle, answer the first lookup with a public address and the second with a private one (DNS rebinding), and reach an address our check meant to block.

We accept this residual risk rather than build a custom connection layer to close it, because:

  • A successful rebind buys one request, not a standing session — the address is re-checked on the very next hop, so it can't be used to keep a private address in reach.
  • It can only affect the domain being verified or crawled at that moment. It has no path to another customer's data or another account.
  • The domain verification rate limit above bounds how many times anyone can attempt the race against a given domain.

Query logging and analytics

Query text is stored with a salted, rotating hash of the caller — never a raw IP address and never an identifiable user ID — for up to 90 days, after which it is dropped and cannot be recovered by anyone, including us. Query logging can be switched off per index if you would rather not record query text for it at all. See Reading analytics and the Privacy policy for the full data inventory.

Crawler behavior and SuoBot

The crawler identifies itself as SuoBot in its user agent, and only crawls domains that have completed domain verification — a DNS TXT record or a meta tag. It:

  • Obeys robots.txt, including a specific SuoBot block if you add one, and honors crawl-delay.
  • Never authenticates, never sends cookies, and never submits a form — it only follows links and reads server-rendered HTML.
  • Runs no JavaScript, so it cannot see content a script renders client-side.

Blocking SuoBot

Add a SuoBot rule to your robots.txt to exclude specific paths, or disallow it entirely:

User-agent: SuoBot
Disallow: /internal/

See the Crawler policy for rate limits and how to reach us about crawler behavior.

Reporting a security issue

If you find a security issue, do not open a public issue or a support ticket — email the address listed in the Crawler policy's contact section, which is monitored for exactly this.

On this page