Apache Iceberg™ is introducing Read Restrictions, an extension to the Iceberg REST Catalog spec. Read Restrictions move column masking and row-level access controls into the catalog, so policy is enforced consistently no matter which engine reads the table.
The goal: one policy, universal enforcement
Interoperable tables are now the standard. An Apache Iceberg table can be written by one engine and read by any other. But "read by anyone" falls short of what users actually need. In most real-world deployments, access to a table is not all-or-nothing; every user gets personalized access based on their particular privileges. Alice may be allowed to see Social Security numbers in full, while Bob sees only the last four digits. Candice may browse information for US users of the system, while Devin sees only Spanish users' data. Traditional table-level read permissions cannot describe this kind of fine-grained access control (FGAC).
While an Iceberg table is portable, the FGAC policies attached to it have traditionally been tied to a specific engine or platform. A policy written in one system won't apply on another, leading to duplicated policies or to reliance on third-party systems decoupled from the catalog that actually owns the tables. This split-brain situation creates a governance and auditing mess: knowing who has access to what becomes a systems design question in itself.
The newly merged Read Restrictions extension closes this gap by putting FGAC directly into the Iceberg REST Catalog specification. A client loading a table now receives the Iceberg metadata, along with a set of column masking and row filtering instructions. An administrator designates which engines are trusted to apply those instructions before returning data to the user. The policy itself stays in the catalog, but the access control decisions flow with the table directly to the engine responsible for enforcement. One policy, universal enforcement.

What the spec changes:
Read Restrictions add two pieces of access control information to the loadTable response: required-row-filter, a row filter the reader must apply; and required-column-projections, a list of columns the reader must mask per the given action. Both are scoped to the principal who made the request. The same table can come back with different instructions for a different caller or the same caller at different times as policies and roles change.
Row filtering: required-row-filter is an Iceberg predicate like "country = USA". One can use primitives like =, !=, >, <, IN, NOT IN. JOINs clauses such as "EXISTS" or "NOT EXISTS," can be expressed in these primitives if the existence checks are on tables of reasonable size in the policy. Complex JOINs, on the other hand, might still not be expressible. Any row evaluated as false must not appear in the result. And nothing derived from those rows may leak into it, either.
In column masking, required-column-projections list columns, each has one of nine masks. Each mask is specified exactly. Two engines implementing the spec independently produce identical output, making the output truly interoperable.
The nine masks:
| Mask Name | Description |
|---|---|
| mask-alphanum | digits become n, other characters become x; ( ) , . - @ are preserved |
| mask-to-fixed-value | a single fixed replacement value |
| replace-with-null | NULL |
| show-first-4 | only the first four characters survive; the rest are masked |
| show-last-4 | only the last four characters survive; the rest are masked |
| truncate-to-year | the date or timestamp truncated to year |
| truncate-to-month | the date or timestamp truncated to month |
| sha-256-global | SHA-256, deterministic everywhere: the same input always hashes to the same output, so hashed columns still join |
| sha-256-query-local | SHA-256 with a fresh random salt per query: consistent within one query, unlinkable across them |
So 4111-1111-1111-4444 under show-last-4 comes back as nnnn-nnnn-nnnn-4444, and a.analyst@example.com under mask-alphanum comes back as x.xxxxxxx@xxxxxxx.xxx.
Although you can't express some more complicated access control rules available in other systems, remember that what the catalog sends as part of read restrictions is the user-personalized restriction. The policy itself can be far more complicated and utilize concepts that only the Catalog can understand. Like having a policy dynamically change based on an attribute of the user querying the table, or referring to Catalog primitives like cost-center.
How: enforcement
A catalog can define a policy using whatever semantics the platform supports. But when the caller accesses the resource protected by policy in the catalog, the catalog gives a representation of that policy that is caller-specific via read restrictions over the IRC protocol. A catalog can always choose to deny access to the caller if the policy is not expressible to read restrictions.
Because the access controls are enforced directly by the engine, a governance-enforced copy of the data doesn't need to be produced and all predicate pushdowns still work as expected. This gives you all the performance of a native Iceberg table, but with finer-grained access controls enabled. In exchange, the catalog has to trust that the engine the caller is using will actually apply it consistently, which is why masks must be pinned to the byte. This model only holds if every participant agrees on precisely what each one means.
It's fail-closed. If a reader supports read restrictions and cannot fully apply what it was handed with an unrecognized mask, an expression it cannot parse, or a projection it does not know how to honor, it must fail the query. This means no raw data, no partial data, no empty results. For a governance primitive, silence is the one unacceptable failure mode.
All of this requires a shared trust model between the admin of the platform and the catalog that certifies whether the client's engine can be trusted. If handed over the read restriction, it will reliably enforce them. Advanced engines such as Snowflake® support additional guarantees like plan hiding and protections against predicate reorder attack.
Trust can be established between catalog and the engine in multiple ways; some industry standards include mTLS, on-behalf-of flow, and similar methods. Since this uses a bilateral agreement between the catalog and the engine or administrator, and is very platform-specific, it's not something defined in the spec. The catalog is in full autonomy to decide when and who to give read restrictions to. (Check out this talk from iceberg summit 2026 for details: Iceberg's Iron Wall: Open Governance via IRC for Enforcing Row and Column Security.)
Future work
Iceberg made table data interoperable: write with one engine, read with any other. The REST catalog made metadata interoperable: one catalog, any client. Read Restrictions are the same move applied to governance: One policy, enforced the same way regardless of which engine reads the table.
The Read Restrictions spec is the first step on this journey to full interoperable governance. With this move, we have defined the API and shown the model works in end-to-end proofs of concept (POCs). (See API: Add ReadRestrictions, E2E with Iceberg Generics, E2E with Apache Spark™, E2E with Trino and RCK for servers to verify spec). Next we need to ensure that engines can work with these systems. By starting this work, we are beginning to build the web of interconnected trusted systems that will form the Open Data and Open Governance architecture of the future.



