skip to content

A reporting role was granted SELECT on every table in a schema, and a week later new tables in that same schema are unreadable to it. Why does that happen, and how is it normally handled?

level: seniorimportance: should knowfreq 35%

answer

  1. bulk grant expands once, per existing object
  2. new object = owner rights only
  3. default privileges keyed to the CREATOR role
  4. grants belong in the migration
  5. failure is silent -- add a drift check

basics

~20 s

A bulk grant over a schema is expanded once, at execution time, into one stored privilege per existing object. Objects created later have no such privilege. Fixes: engine-level default-privilege rules keyed to the creating role, granting the privileges in the same migration that creates the object, or a reconciliation job.

solid answer

~60 s

Privileges are stored per object. A statement that grants on 'all tables in a schema' is syntactic sugar: the engine enumerates the tables that exist right then and records one grant each. It does not create a standing rule about the schema, so anything created afterwards starts with only its creator's implicit rights. Three standard remedies: 1. **Default-privilege rules.** Most engines let you declare that objects created *by a given role* in a given schema automatically carry certain grants. The trap is the keying: the rule applies to the creator, so if your migration runner changes identity, the rule silently stops firing. 2. **Grants in the migration.** Every DDL change that creates an object ships its grants in the same change set, enforced by review or a lint rule. Explicit and auditable, but relies on discipline. 3. **Access through a stable indirection.** Expose reporting through views owned by one role, or reconcile periodically with a job that re-applies the intended grants and alerts on drift. In practice I use default privileges plus a drift check, because either one alone fails quietly.

code

sql · 3 lines
sql
GRANT SELECT ON ALL TABLES IN SCHEMA app TO reporting;  -- expanded now
CREATE TABLE app.invoices (...);                        -- not covered
GRANT SELECT ON app.invoices TO reporting;              -- must be issued too

go deeper

for a junior

Know that grants are per object and that a bulk grant does not cover tables created later.

for a middle

Name the remedies -- default privileges or grants inside the migration -- and that new objects start with owner rights only.

for a senior

Discuss the creator-keying trap, the silent failure mode, and pairing a mechanism with drift detection or a reconciliation job.

for a principal

Decide the org pattern: access declared with the schema, curated views for broad consumers, and an automated review that proves intended access matches actual.

## Why bulk grants do not persist The privilege system stores an access control list per object. There is no data structure meaning 'this role may read anything that ever appears in this schema' -- the schema-level privilege only governs name resolution within the namespace, not the contents. So a bulk grant is expanded at execution: the engine resolves the object list once and writes one privilege entry per object. That is why the behaviour looks like a bug and is not one. The grant did exactly what it said, for the objects that existed when it ran. ## The creator's implicit rights A newly created object has an owner (normally the role that created it) with implicit full rights, and nothing else. Every other access must be granted. This is why the failure mode is asymmetric: the migration user and any superuser-equivalent see the new table immediately, while the reporting role does not -- so the problem is usually discovered by a downstream consumer, hours or days later, rather than by the person who ran the DDL. ## Remedy 1: default privilege rules Engines commonly provide a mechanism that says: 'when role X creates an object of type T in schema S, automatically apply these grants'. It is the cleanest fix, and it has one sharp edge worth naming in an interview: **the rule is keyed on the creating role**, not on the schema alone. Consequences: - If tables are usually created by the migration user but someone creates one manually as themselves, the new table has no grants. - If the deployment pipeline's database identity changes -- a new service account, a rotated credential mapped to a different role -- every subsequently created object silently misses its grants. - The rules must be declared per object type; tables, sequences and routines are separate declarations, and forgetting sequences is a classic cause of an INSERT failing while a SELECT works. ## Remedy 2: grants in the migration Treat access as part of the schema definition: the change set that creates a table also contains the grants for it. Advantages: fully explicit, versioned, reviewable, and immune to the creator-identity trap. Disadvantages: it is a human convention, so it decays unless enforced by review checklist or an automated check that fails CI when a new object appears without grants. ## Remedy 3: stable indirection and reconciliation Two complementary patterns: - **Indirection.** Consumers read through views (or a dedicated reporting schema) owned by a single role. New base tables do not need consumer grants at all -- only the curated view does, and adding it is a deliberate act. This also gives you column and row shaping for free. - **Reconciliation.** A scheduled job compares actual privileges against the intended model and either reports drift or re-applies it. This is the safety net that catches whatever the other mechanisms missed, and it doubles as the access-review artefact auditors ask for. ## Choosing For an application's own service accounts, migration-time grants are usually right: the access model is small and belongs with the schema. For broad consumers such as analytics roles, default privileges plus a drift check scale better because the object set changes constantly. The strongest answer says that whichever mechanism you choose, the failure is *silent*, so a detection mechanism is not optional -- alert on 'objects in schema S with no grant to the expected consumer role'. ## Related gotcha The mirror-image case is a bulk REVOKE: it also applies only to objects existing at the time, so revoking 'all tables in a schema' does not stop the role reading a table created after the revoke. Any offboarding or lockdown procedure that relies on a bulk revoke needs the same follow-up.

  • Default privileges were configured but new tables still lack grants. What is the first thing you check?
    Which role actually created the tables. Default-privilege rules are keyed to the creating role, so a table created by a person, a new deployment service account, or a rotated identity does not match a rule declared for the previous migration user. Confirm the object owner in the catalog, then either add a rule for that role or standardise so all DDL runs as one identity.
  • Does the same problem exist for a bulk REVOKE?
    Yes, symmetrically. Revoking on all tables in a schema removes only the privileges recorded for objects that exist at that moment, so a table created afterwards under a default-privilege rule or an explicit migration grant will be readable again. Lockdown and offboarding procedures therefore need to remove the default-privilege rules and the role memberships as well, not just run one bulk revoke.

saying these in an interview costs you the question

  • Believing a bulk grant creates a standing rule for the schema
  • Assuming schema-level privilege implies access to the tables inside it
  • Configuring default privileges without noticing they are keyed to the creating role
  • Relying on convention alone with no drift detection
  • Treating a bulk revoke as a complete lockdown

context