{"id":"GHSA-c8jx-96c9-8xrp","aliases":[],"url":"https://o3.security/vulnerability/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.","published":"2026-07-01T20:22:06Z","modified":"2026-07-01T20:30:08.860446971Z","cvss":{"score":4.3,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"crates.io","name":"surrealdb","fixedVersion":"3.1.0"}],"fix":{"url":"https://github.com/surrealdb/surrealdb/pull/240","label":"surrealdb/surrealdb#240"},"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"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-07-01T20:30:08.860446971Z"}}