{"id":"GHSA-c8jx-96c9-8xrp","summary":"SurrealDB: Field-level SELECT permissions bypassed via indexed COUNT fast paths","details":"A record user could learn the value of a hidden field by counting how many records match a guess.\n\nWhen `DEFINE FIELD ... PERMISSIONS FOR select WHERE ...` hides a field's contents from a caller, and that field is indexed, running `SELECT count() FROM t WHERE hidden_field = \"guess\" GROUP ALL` returned a count greater than zero whenever a record actually had that value — even though the caller was never allowed to read the field directly. The query planner used an indexed-COUNT shortcut (`Index::Count`, `IndexCountScan`, or the legacy `Iterate Index Count` / `Iterate Index Keys` paths) that counts matching index entries and skips the permission check that would normally hide the value. The same query with `WITH NOINDEX` correctly returned `[]`, confirming the gap.\n\nBy repeating the count query with different guesses, an attacker can confirm or recover the contents of any restricted field they could not read through a normal `SELECT`.\n\n### Impact\n\nWhat an attacker **can** do:\n\n- Confirm or recover values of a field protected by field-level SELECT permissions on any table they hold table-level SELECT on, provided the field is indexed.\n- Repeat the query with different guesses to read restricted field contents one value at a time.\n\nWhat it **can't** do:\n\n- Read fields that are not indexed (the shortcut only fires when an index covers the predicate column).\n- Cross table, database or namespace isolation boundaries.\n- Modify data, escalate privileges, or affect availability.\n\n### Patches\n\nThe legacy planner (`surrealdb/core/src/idx/planner/tree.rs`) and the streaming planner (`surrealdb/core/src/exec/planner/select/mod.rs`) now both refuse the indexed fast path when the WHERE / ORDER tree references a field governed by a non-`Full` SELECT permission:\n\n- `resolve_indexes` skips any B-tree / unique index whose columns are governed by such a permission.\n- A new `cond_touches_restricted_field` flag is propagated; `eval_count` refuses a dedicated `Index::Count` when set.\n- The streaming planner adds `cond_touches_restricted_select_field`, a `RestrictedIdiomChecker` visitor that matches each idiom against the table's field-permission prefixes (loaded via the plan-time txn), and gates `IndexCountScan` emission on it.\n- The fast paths are preserved for root / owner sessions via `should_check_perms_for_view`.\n\nVersions 3.1.0 and later are not affected.\n\n### Workarounds\n\nUsers unable to patch are advised to consider the following workarounds:\n\n- Avoid `DEFINE INDEX` on fields whose values are protected by field-level SELECT permissions. The class of attack is specific to the indexed fast paths.\n- Restrict the ability of record users to issue arbitrary `SELECT count() … GROUP ALL` queries against tables containing field-protected columns.\n- Use namespace / database isolation as the primary boundary where feasible.","modified":"2026-07-01T20:30:08.860446971Z","published":"2026-07-01T20:22:06Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-07-01T20:22:06Z","nvd_published_at":null,"cwe_ids":["CWE-863"]},"references":[{"type":"WEB","url":"https://github.com/surrealdb/surrealdb/security/advisories/GHSA-c8jx-96c9-8xrp"},{"type":"WEB","url":"https://github.com/surrealdb/surrealdb/pull/240"},{"type":"WEB","url":"https://github.com/surrealdb/surrealdb/commit/0c6dd021bb55b32a78a553c72bb9c0cdd414825f"},{"type":"PACKAGE","url":"https://github.com/surrealdb/surrealdb"}],"affected":[{"package":{"name":"surrealdb","ecosystem":"crates.io","purl":"pkg:cargo/surrealdb"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.1.0"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-c8jx-96c9-8xrp/GHSA-c8jx-96c9-8xrp.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"}]}