{"uuid": "227ef8e7-e525-4064-9ca1-ec6eb58f6bdc", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2020-25695", "type": "seen", "source": "https://gist.github.com/RussellSpitzer/0d2d321390ae7ea83b744e990eab8dc0", "content": "# How databases permission indexes\n\nA survey of 20 systems across relational, cloud DW, lakehouse, NoSQL, and search\ncategories \u2014 grouped by how each system authorizes index DDL (or refuses to).\n\n## At a glance\n\n| Metric | Count |\n|---|---|\n| Systems surveyed | 20 |\n| Grantable index DDL (separate priv, scoped variant, or IAM action) | 9 |\n| Owner-only (no GRANT path) | 3 |\n| No first-class indexes (or index *is* the data unit) | 5 |\n\n## The big split\n\nOf the systems with a real index concept, almost none give indexes their own ACL\nsurface. The only widely-used pattern that does is the classic SQL `GRANT INDEX`\nfrom MySQL / Oracle / Db2 \u2014 and even there, `DROP` usually flows through table\nor index `CONTROL`. Cloud RBAC systems (BigQuery, MongoDB) expose `createIndex`\nas a discrete action, but it's bundled into the same roles that own table\nwrites. Everyone else folds index DDL into `ALTER` / ownership of the table.\n\n## All systems\n\n| System | Category | Pattern | Mechanism | Docs |\n|---|---|---|---|---|\n| **MySQL** | RDBMS | INDEX privilege | `INDEX privilege` | [MySQL 8.4 \u2014 Privileges Provided](https://dev.mysql.com/doc/refman/8.4/en/privileges-provided.html#priv_index) |\n| **MariaDB** | RDBMS | INDEX privilege | `INDEX privilege` | [MariaDB GRANT \u2014 Table Privileges](https://mariadb.com/kb/en/grant/#table-privileges) |\n| **Oracle Database** | RDBMS | INDEX privilege | `INDEX object privilege` | [Oracle 23ai \u2014 GRANT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/GRANT.html) |\n| **IBM Db2** | RDBMS | INDEX privilege | `INDEX (table) + CONTROL (index)` | [Db2 11.5 \u2014 GRANT (index privileges)](https://www.ibm.com/docs/en/db2/11.5.x?topic=statements-grant-index-privileges) |\n| **PostgreSQL** | RDBMS | Owner-only | `Table ownership required` | [PostgreSQL 17 \u2014 CREATE INDEX](https://www.postgresql.org/docs/current/sql-createindex.html) |\n| **SQLite** | RDBMS | Owner-only | `No authorization model` | [SQLite \u2014 CREATE INDEX](https://www.sqlite.org/lang_createindex.html) |\n| **DuckDB** | Embedded analytics | Owner-only | `No GRANT system (single-user)` | [DuckDB \u2014 Indexes](https://duckdb.org/docs/stable/sql/indexes.html) |\n| **SQL Server** | RDBMS | ALTER-folded | `ALTER on table` | [SQL Server \u2014 CREATE INDEX (Permissions)](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-index-transact-sql) |\n| **CockroachDB** | Distributed SQL | ALTER-folded | `CREATE on table` | [CockroachDB \u2014 CREATE INDEX (Required Privileges)](https://www.cockroachlabs.com/docs/stable/create-index) |\n| **Cassandra** | Wide-column NoSQL | ALTER-folded | `ALTER permission on table/keyspace` | [Cassandra 4.1 \u2014 CQL Security](https://cassandra.apache.org/doc/4.1/cassandra/cql/security.html) |\n| **Snowflake** | Cloud DW | Scoped variant | `ADD SEARCH OPTIMIZATION on schema` | [Snowflake \u2014 Enabling Search Optimization](https://docs.snowflake.com/en/user-guide/search-optimization/enabling) |\n| **Databricks SQL / Delta** | Lakehouse | Scoped variant | `MODIFY on table` | [Databricks \u2014 Unity Catalog Privileges](https://docs.databricks.com/aws/en/data-governance/unity-catalog/manage-privileges/privileges) |\n| **Apache Hudi** | Lakehouse table format | Scoped variant | `Engine ALTER on table` | [Apache Hudi \u2014 Metadata Indexing](https://hudi.apache.org/docs/metadata_indexing/) |\n| **BigQuery** | Cloud DW | IAM action | `bigquery.tables.createIndex` | [BigQuery \u2014 Introduction to Search (Roles &amp; Permissions)](https://cloud.google.com/bigquery/docs/search-intro) |\n| **MongoDB** | Document NoSQL | IAM action | `createIndex / dropIndex actions` | [MongoDB \u2014 Privilege Actions](https://www.mongodb.com/docs/manual/reference/privilege-actions/) |\n| **Elasticsearch** | Search | Index = data | `Index-level RBAC` | [Elasticsearch \u2014 Security Privileges](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html) |\n| **OpenSearch** | Search | Index = data | `Index-level RBAC` | [OpenSearch \u2014 Permissions](https://docs.opensearch.org/latest/security/access-control/permissions/) |\n| **Apache Iceberg** | Lakehouse table format | N/A | `No first-class indexes` | [Apache Iceberg \u2014 Table Spec](https://iceberg.apache.org/spec/) |\n| **Amazon Redshift** | Cloud DW | N/A | `No user-defined indexes` | [Redshift \u2014 Designing Tables Best Practices](https://docs.aws.amazon.com/redshift/latest/dg/c_designing-tables-best-practices.html) |\n| **Apache Hive** | Hadoop SQL | N/A | `Indexes removed in Hive 3.0 (HIVE-18448)` | [HIVE-18448 \u2014 Drop Support for Indexes](https://issues.apache.org/jira/browse/HIVE-18448) |\n\n## Patterns\n\n### 1. Dedicated `INDEX` privilege \u2014 explicit, grantable, table-scoped\n\nA first-class privilege exists specifically for index DDL on a table. The DBA\ncan give an analyst the right to add indexes without granting `ALTER` or\nownership.\n\n- **MySQL** \u2014 `GRANT INDEX ON db.tbl TO user` \u2014 grantable independently of\n  `ALTER`, `SELECT`, etc.\n- **MariaDB** \u2014 inherited from MySQL; same `GRANT INDEX` surface.\n- **Oracle Database** \u2014 `INDEX` object privilege on another schema's table lets\n  a user create an index on it. `CREATE ANY INDEX` is the system-wide variant.\n- **IBM Db2** \u2014 table-level `INDEX` privilege authorizes index creation. The\n  created index has its own `CONTROL` privilege controlling `DROP`, which\n  auto-grants to the creator.\n\n### 2. Folded into `ALTER` / `CREATE` on the table\n\nIndex DDL is implicitly covered by whatever privilege governs schema-changing\noperations on the table. The system treats indexes as table internals, not as\nseparately-securable objects.\n\n- **SQL Server** \u2014 no separate `INDEX` privilege. `ALTER` (or `CONTROL`) on the\n  table grants the right to `CREATE` / `ALTER` / `DROP` its indexes.\n- **CockroachDB** \u2014 index DDL gated by `CREATE` on the table.\n- **Cassandra** \u2014 `GRANT ALTER ON KEYSPACE ks TO role` covers `CREATE INDEX` /\n  `DROP INDEX` on tables in that keyspace. No separate index-permission tier.\n\n### 3. Table owner only \u2014 no GRANT path\n\nIndex creation cannot be delegated through the permission system at all; only\nthe table owner (or a superuser) may run it. Either there is no `INDEX`\nprivilege, or there is no permission model at all.\n\n- **PostgreSQL** \u2014 no `INDEX` privilege exists. `CREATE INDEX` must be run as\n  the table owner (or a member of its role). `REINDEX` likewise. (Driven in\n  part by the fact that expression indexes evaluate user-defined functions as\n  the table owner \u2014 see CVE-2020-25695 for the historical context.)\n- **SQLite** \u2014 no GRANT/REVOKE system at all. File-level OS permissions are the\n  only access control.\n- **DuckDB** \u2014 embedded engine with file/process-level isolation. No role\n  model, so index DDL is gated only by write access to the database file.\n\n### 4. Scoped privilege on an index-like feature\n\nSystems without classical indexes still expose index-shaped accelerators\n(search optimization, Z-order, bloom filters, multi-modal indexes). These get\ntheir own privileges \u2014 often split across the table and its container.\n\n- **Snowflake** \u2014 no B-tree indexes. The Search Optimization Service is the\n  closest equivalent. Enabling or dropping it requires `OWNERSHIP` of the\n  table **and** `ADD SEARCH OPTIMIZATION` on the containing schema (the latter\n  enforcement was tightened in [BCR-1046, 2023_03 bundle](https://docs.snowflake.com/en/release-notes/bcr-bundles/2023_03/bcr-1046)).\n- **Databricks SQL / Delta** \u2014 Z-ORDER, bloom filter, and (preview)\n  liquid-clustering indexes are table properties; require `MODIFY` (or `OWNER`)\n  on the Unity Catalog table.\n- **Apache Hudi** \u2014 multi-modal index types (bloom, record, column-stats) are\n  managed via Spark/Flink `ALTER TABLE`. ACLs are delegated to whichever\n  catalog/engine binds the table.\n\n### 5. Discrete IAM action\n\nPermissions are modeled as fine-grained actions in an IAM/RBAC system, and\n`createIndex` is one of them. Granular enough to be granted in custom roles,\nindependent of the broader table-update permission.\n\n- **BigQuery** \u2014 `bigquery.tables.createIndex` is a discrete IAM permission\n  alongside `createSnapshot`, `updateData`, etc. Distinct from\n  `bigquery.tables.update`. Bundled into Data Editor / Data Owner / Admin\n  roles. Paired with `bigquery.tables.deleteIndex` and\n  `bigquery.tables.updateIndex`.\n- **MongoDB** \u2014 role-based: `createIndex`, `dropIndex`, `createSearchIndexes`,\n  `listIndexes` are separate privilege actions scoped to db or collection.\n  Bundled into built-in roles like `readWrite` and `dbAdmin`.\n\n### 6. Index is the data container\n\nIn search engines, an index is the primary data unit. Permissions are scoped to\nthe index itself (often per pattern), and there is no distinction between\ntable privileges and index privileges.\n\n- **Elasticsearch** \u2014 `create_index` / `manage` / `all` are role privileges\n  granted per index pattern.\n- **OpenSearch** \u2014 fork of Elasticsearch. Same model: `create_index` and\n  `manage` actions granted per index/index-pattern via roles.\n\n### 7. No first-class indexes \u2014 nothing to permission\n\nThe system intentionally has no secondary index concept. Acceleration comes\nfrom layout/clustering/stats, and ACLs live at the table level.\n\n- **Apache Iceberg** \u2014 table-level stats live in metadata + Puffin sidecars.\n  No 'index' object in the spec; ACLs are delegated to the catalog (REST,\n  Polaris, Unity, Glue, Hive Metastore).\n- **Amazon Redshift** \u2014 uses sort keys / dist keys / zone maps in place of\n  secondary indexes. Nothing to permission.\n- **Apache Hive** \u2014 index DDL was deprecated and removed in Hive 3.0\n  ([HIVE-18448](https://issues.apache.org/jira/browse/HIVE-18448)); the\n  historical model treated indexes as auxiliary tables with table-equivalent\n  privileges.\n\n## Cross-cutting observations\n\n### Create vs. drop are often split\n\nIn Db2 the table's `INDEX` privilege authorizes `CREATE`, but dropping that\nindex requires `CONTROL` on the index object \u2014 auto-granted only to the\ncreator. Oracle splits `INDEX` (per-table) from `CREATE ANY INDEX` /\n`DROP ANY INDEX` (system-wide). Snowflake requires the schema-level\n`ADD SEARCH OPTIMIZATION` for *both* add and drop, but only after enforcing\nownership of the table. Even systems that grant index DDL rarely treat the two\nverbs symmetrically.\n\n### Read permissions never gate index *use*\n\nAcross every surveyed system, querying an indexed table only requires the table\nread privilege. Snowflake says it explicitly: search optimization is a table\nproperty detected at planning time, so `SELECT` is enough. No system requires a\nseparate \"use index\" grant \u2014 index existence is metadata that any reader\nbenefits from automatically.\n\n### Cost-bearing accelerators get their own gate\n\nWhen the index-like structure costs money or compute to maintain (Snowflake\nsearch optimization, BigQuery search indexes, Databricks bloom / Z-order), the\nsystem tends to add a discrete create/drop privilege \u2014 even if it folds *use*\ninto `SELECT`. The privilege is really about controlling spend and storage\ngrowth, not about semantic visibility.\n\n### Indexes are not portable security objects\n\nNo surveyed system permissions an index *independently* of its table for reads.\nYou cannot grant `SELECT` on an index but not the underlying table. The closest\nexception is Elasticsearch / OpenSearch, where the \"index\" *is* the table \u2014\nand even there permissions are per-index, not per-secondary-structure.\n\n## What this means for Apache Iceberg\n\nIceberg has no first-class index today. If a future spec adds one (Puffin-backed\nbitmap / vector / inverted index, multi-modal indexes \u00e0 la Hudi), the survey\nsuggests three precedents worth weighing \u2014 none of which require inventing a\nnew ACL primitive:\n\n1. **Fold into `ALTER` / `MODIFY` on the table.** Matches SQL Server, Cassandra,\n   CockroachDB, Databricks, and Hudi. Cheapest for catalogs (REST, Polaris,\n   Unity, Glue) since no new permission needs to be plumbed.\n2. **Add a scoped `MANAGE INDEX` privilege at the table or namespace.** Matches\n   Snowflake's pattern when index maintenance has real cost (Puffin file\n   rewrites, background compaction). Lets a platform team grant\n   \"create-indexes-but-not-rewrite-data.\"\n3. **Stay silent in the spec; defer to the catalog.** Matches Iceberg's current\n   posture on writes and snapshot management. Lowest spec surface; pushes the\n   choice to REST / Polaris / Unity to implement consistently.\n\nThe historical SQL `INDEX` privilege (MySQL, Oracle, Db2) is the outlier\npattern \u2014 modern systems do not reach for it.\n\n---\n\n*Sources are linked inline. Survey conducted June 2026 against current docs at\ntime of writing.*\n", "creation_timestamp": "2026-06-08T16:51:11.000000Z"}