{"schema":"threatline.record/2","record":{"id":"GHSA-2jc3-58q7-mm2j","aliases":["GHSA-2jc3-58q7-mm2j"],"summary":"The `revoke` RPC invalidates any user's refresh token without authentication or proof of possession","details":"SurrealDB record access methods defined `WITH REFRESH` issue a refresh token that a client can later exchange for new access tokens. The `revoke` RPC method invalidates such a refresh token. That method performed no authentication and no proof of possession: it took the target namespace, database, and access method from an access token that was decoded without verifying its signature, took the grant identifier from a refresh string that was only format-checked, and then carried out the revocation as root. It never verified that the caller actually held the refresh token's key.\n\nA grant identifier is not a secret. `ACCESS <ac> SHOW ALL` reveals every grant's `id` (only the key is redacted) and requires only `VIEWER`-level view permission on the access method, and the identifier is embedded in every issued refresh token. So any caller who learned a grant identifier — a low-privileged database user, or anyone to whom a token leaked — could permanently revoke the corresponding refresh token without holding its key. This turned an operation that over SurrealQL (`ACCESS ... REVOKE GRANT`) requires the Owner role into one reachable by a low-privileged, or in principle unauthenticated, RPC caller.\n\nThe victim's refresh token was then permanently invalidated: every subsequent refresh failed, forcing re-authentication.\n\n### Impact\n\nThis affects deployments that use record access methods defined `WITH REFRESH` and expose the `/rpc` endpoint (the default), where an attacker can learn a grant identifier — for example through any database-level account (including `VIEWER`) via `ACCESS <ac> SHOW ALL`, or through a leaked refresh token.\n\nWhat an attacker **can** do:\n\n- Permanently revoke another user's refresh token by supplying its grant identifier to the `revoke` RPC, without holding the token's key.\n- Enumerate all grant identifiers for an access method with only `VIEWER`-level view permission, and revoke every user's refresh token for that method, denying refresh to all of its users.\n\nWhat it **can't** do:\n\n- Recover the refresh token's key, sign in as the victim, or read the victim's data — this is not account takeover or data disclosure. The victim can re-authenticate with their credentials.\n- Guess a grant identifier without a disclosure channel; the identifier carries roughly 68 bits of entropy, so a low-privilege or token-leak channel is required in practice.\n- Affect deployments that do not use `WITH REFRESH`.\n\n### Patches\n\nRevoking a refresh token now requires proof of possession. The presented refresh key is verified with a constant-time comparison against the stored grant — the same check the refresh (sign-in) path already performs — before anything is revoked, so only the holder of the token (or an Owner acting through SurrealQL) can revoke it. Every failure is reported as an opaque authentication error, so a caller cannot distinguish a wrong key from a missing, expired, or already-revoked grant.\n\n- Versions 3.3.0 and later are not affected by this issue.\n\n### Workarounds\n\nUsers unable to patch should consider the following workarounds:\n\n- Deny the `revoke` RPC method via capabilities where refresh-token revocation is not required (for example `--deny-rpc revoke`, env `SURREAL_CAPS_DENY_RPC`).\n- Restrict network access to the `/rpc` endpoint to trusted clients.\n- Treat grant identifiers as sensitive: limit who holds database-level accounts that can run `ACCESS <ac> SHOW ALL`, since it discloses grant identifiers.\n\n### References\n\n- [SurrealQL Documentation — DEFINE ACCESS](https://surrealdb.com/docs/surrealql/statements/define/access)\n- [SurrealDB Documentation — RPC protocol](https://surrealdb.com/docs/surrealdb/integration/rpc)\n- [SurrealDB Documentation — Capabilities](https://surrealdb.com/docs/surrealdb/security/capabilities)\n- `fix(iam): require proof of possession to revoke a refresh token` (included in SurrealDB 3.3.0)\n\n### Acknowledgements\n\nThank you to [Yanhaoxi](https://github.com/Yanhaoxi) for reporting this issue.\n","categories":["Unclassified"],"product":"surrealdb","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2jc3-58q7-mm2j","source_modified_at":"2026-09-28T11:15:26Z","fetched_at":"2026-10-26T12:48:13.248886Z","package":{"ecosystem":"rust","name":"surrealdb"},"range":">= 3.0.0, < 3.3.0"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2jc3-58q7-mm2j","source_modified_at":"2026-09-28T11:15:26Z","fetched_at":"2026-10-26T12:48:13.248886Z","package":{"ecosystem":"rust","name":"surrealdb"},"version":"3.3.0"}],"cvss":6.5,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-28T11:15:26Z","modified_at":"2026-09-28T11:15:26Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2jc3-58q7-mm2j","fetched_at":"2026-10-26T12:48:13.248886Z","source_modified_at":"2026-09-28T11:15:26Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-28T11:15:26Z","content_hash":"863a2d7e7704b9b6e229677ec4639dbcc59cc088cc1485a0c91b117da071bae8"}}
