The Problem with Pre-23ai Privilege Management

Before Oracle 23ai, DBAs managing application access had exactly two options when a service account needed to query a schema:

Option 1 — Grant on each individual object

GRANT SELECT ON hr.employees    TO app_user;
GRANT SELECT ON hr.departments  TO app_user;
GRANT SELECT ON hr.jobs         TO app_user;
GRANT SELECT ON hr.locations    TO app_user;
-- ... repeat for every table and view in the schema

This is correct from a security standpoint but becomes a maintenance nightmare the moment a new table is added. Every new object requires a new grant. Scripts drift. Service accounts end up missing access to new tables and applications break in production.

Option 2 — Grant ANY privilege

GRANT SELECT ANY TABLE TO app_user;

One line. Problem solved — until it isn't. This grants the service account read access to every table in the entire database, including tables in SYS, SYSTEM, and every other schema. If this user account is compromised, your entire database can be compromised. It also violates the principle of least privilege and most compliance frameworks (PCI-DSS, SOC 2, ISO 27001) outright.

Object privileges have always worked on two tiers only — you get access to a single object, or you get access to every object. However, in the real world of database usage, we generally work in three tiers. Some people need access to objects on an individual basis, some people need access to all objects, but there is a common third tier that sits between these two: the schema.

Oracle 23ai finally closes this gap.


Schema Privileges — The Missing Middle Tier

Oracle Database 23ai introduces a new schema-level grant. If you GRANT SELECT ANY TABLE ON SCHEMA HR TO BOB, that user can see all the tables and views in the HR schema — and only in the HR schema.

-- Before 23ai: dangerous broad grant
GRANT SELECT ANY TABLE TO app_user;

-- Oracle 23ai: scoped to one schema only
GRANT SELECT ANY TABLE ON SCHEMA hr TO app_user;

Same ANY keyword — but with ON SCHEMA hr appended, the privilege is now scoped entirely to the hr schema. The user cannot access any other schema.

Future Objects Are Covered Automatically

If a new table is added to the schema, they instantly have access to that new table. No extra management is needed, and you continue to support a least-privilege security model with appropriate separation of duties.

-- Grant schema privilege once
GRANT SELECT ANY TABLE ON SCHEMA hr TO app_user;

-- Create a new table months later
CREATE TABLE hr.salary_bands (
  band_id    NUMBER PRIMARY KEY,
  band_name  VARCHAR2(50),
  min_salary NUMBER,
  max_salary NUMBER
);

-- app_user can already query it — no additional grant needed
SELECT * FROM hr.salary_bands; -- works immediately

This eliminates the entire class of "new table, forgot to grant" production incidents.


Supported Privilege Types

Many system privileges can be scoped to a schema using ON SCHEMA. The most commonly used ones are:

Privilege Use case
SELECT ANY TABLE Read access to all tables and views in the schema
INSERT ANY TABLE Insert rows into any table in the schema
UPDATE ANY TABLE Update rows in any table in the schema
DELETE ANY TABLE Delete rows from any table in the schema
EXECUTE ANY PROCEDURE Execute all procedures and functions in the schema
CREATE ANY TABLE Create tables within the schema
ALTER ANY TABLE Alter table definitions in the schema
DROP ANY TABLE Drop tables in the schema
SELECT ANY SEQUENCE Read sequences in the schema
READ ANY TABLE Read-only access (alternative to SELECT for some contexts)

You can combine multiple privileges in a single grant:

-- Grant full DML access on the hr schema
GRANT SELECT ANY TABLE,
      INSERT ANY TABLE,
      UPDATE ANY TABLE,
      DELETE ANY TABLE
ON SCHEMA hr TO app_user;

What is NOT covered by SELECT ANY TABLE

Testing by the community has confirmed one important nuance: SELECT ANY TABLE ON SCHEMA includes views but not sequences. Selecting from a sequence requires a separate SELECT ANY SEQUENCE ON SCHEMA grant.

-- Works — tables and views are included
SELECT * FROM hr.employees;
SELECT * FROM hr.emp_details_view;

-- Fails — sequences need a separate grant
SELECT hr.departments_seq.nextval FROM dual;
-- ORA-41900: missing READ privilege on "HR"."DEPARTMENTS_SEQ"

-- Fix
GRANT SELECT ANY SEQUENCE ON SCHEMA hr TO app_user;

Privileges that cannot be schema-scoped

Not all system privileges support the ON SCHEMA clause. Privileges that apply to the database as a whole (such as CREATE SESSION, CREATE TABLESPACE, ALTER DATABASE) cannot be scoped to a schema — they are database-wide by nature.


Granting Schema Privileges to Roles

Schema privileges can be granted to roles, not just users. This fits cleanly into an existing role-based access control model:

-- Create a read-only role for the HR schema
CREATE ROLE hr_readonly;
GRANT SELECT ANY TABLE ON SCHEMA hr TO hr_readonly;
GRANT SELECT ANY SEQUENCE ON SCHEMA hr TO hr_readonly;

-- Grant the role to users who need read access
GRANT hr_readonly TO alice;
GRANT hr_readonly TO reporting_service;

-- Grant the role to an application service account
GRANT hr_readonly TO hr_app_user;

Now when a new table is added to hr, every user and service account holding hr_readonly automatically gains access — without touching any grant statements.


The WITH ADMIN OPTION

You can grant schema privileges with ADMIN OPTION, which allows the grantee to further grant the privilege to others.

-- Grant with admin option
GRANT SELECT ANY TABLE ON SCHEMA hr TO team_lead WITH ADMIN OPTION;

-- team_lead can now grant to others
GRANT SELECT ANY TABLE ON SCHEMA hr TO junior_dev;

Use WITH ADMIN OPTION carefully — it creates a chain of trust that can be hard to audit. Reserve it for schema owners and senior DBAs.


Who Can Grant Schema Privileges?

Users can grant schema-level privileges on their own schema without having any special privileges. To grant schema-level privileges on someone else's schema, you need either the GRANT ANY SCHEMA or GRANT ANY PRIVILEGE system privilege.

-- Schema owner can grant on their own schema freely
CONN hr_owner/password@FREEPDB1
GRANT SELECT ANY TABLE ON SCHEMA hr TO app_user; -- works

-- DBA granting on another user's schema
CONN / AS SYSDBA
GRANT SELECT ANY TABLE ON SCHEMA hr TO app_user; -- also works

Querying Schema Privilege Grants

Schema privilege grants do not show up in the well-known dictionary views DBA_SYS_PRIVS, DBA_TAB_PRIVS, or DBA_ROLE_PRIVS. Oracle 23ai introduces dedicated views for schema privileges:

View Shows
DBA_SCHEMA_PRIVS All schema privilege grants in the database
USER_SCHEMA_PRIVS Schema privileges granted to the current user
ROLE_SCHEMA_PRIVS Schema privileges granted to roles
SESSION_SCHEMA_PRIVS Schema privileges active in the current session
-- See all schema privilege grants
SELECT grantee, privilege, schema, admin_option
FROM DBA_SCHEMA_PRIVS
ORDER BY schema, grantee;

-- Check what a specific user has
SELECT privilege, schema
FROM DBA_SCHEMA_PRIVS
WHERE grantee = 'APP_USER';

-- Check what roles carry schema privileges
SELECT role, privilege, schema
FROM ROLE_SCHEMA_PRIVS;

Revoking Schema Privileges

Revocation is as clean as the grant:

-- Revoke a single privilege
REVOKE SELECT ANY TABLE ON SCHEMA hr FROM app_user;

-- Revoke multiple privileges
REVOKE INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE
ON SCHEMA hr FROM app_user;

-- Revoke from a role
REVOKE SELECT ANY TABLE ON SCHEMA hr FROM hr_readonly;

The schema-level privilege is not additive/subtractive — revoking an individual object privilege does not remove the schema-level privilege. The schema privilege remains in place and the user retains access through it.

This means if you want to block a user from a specific table inside a schema they have schema-level access to, revoking the individual object grant is not enough. You must revoke the schema privilege and re-grant individual object privileges, or use Virtual Private Database (VPD) policies to restrict row-level access.


Migrating from Legacy GRANT ANY

If your environment currently uses GRANT SELECT ANY TABLE without a schema scope, here is a safe migration path:

Step 1 — Audit current ANY grants

-- Find all users with unscoped ANY privileges
SELECT grantee, privilege
FROM DBA_SYS_PRIVS
WHERE privilege LIKE '%ANY TABLE%'
  AND grantee NOT IN ('SYS', 'SYSTEM', 'DBA')
ORDER BY grantee;

Step 2 — Identify which schemas each grantee actually uses

-- Check actual table access over the past 30 days (requires auditing enabled)
SELECT db_user, obj_schema, COUNT(*) AS access_count
FROM DBA_AUDIT_TRAIL
WHERE action_name = 'SELECT'
  AND timestamp > SYSDATE - 30
GROUP BY db_user, obj_schema
ORDER BY db_user, access_count DESC;

Step 3 — Replace broad grants with schema-scoped grants

-- Revoke the dangerous broad grant
REVOKE SELECT ANY TABLE FROM app_user;

-- Replace with schema-scoped grants for each schema actually needed
GRANT SELECT ANY TABLE ON SCHEMA hr     TO app_user;
GRANT SELECT ANY TABLE ON SCHEMA orders TO app_user;

Step 4 — Verify nothing broke

-- Confirm the new grants are in place
SELECT grantee, privilege, schema
FROM DBA_SCHEMA_PRIVS
WHERE grantee = 'APP_USER';

-- Confirm the old broad grant is gone
SELECT grantee, privilege
FROM DBA_SYS_PRIVS
WHERE grantee = 'APP_USER'
  AND privilege LIKE '%ANY%';

Limitations to Know

Schema privileges cannot be granted for the SYS schema. Attempting to do so returns an error — which is intentional and correct.

-- This will fail — SYS schema cannot be used with schema privileges
GRANT SELECT ANY TABLE ON SCHEMA SYS TO app_user;
-- ORA-65155: operation not allowed on SYS schema

If your application needs access to SYS-owned views (such as V$SESSION or DBA_TABLES), grant those individually as you always have:

GRANT SELECT ON SYS.V_$SESSION TO app_user;
GRANT SELECT ON SYS.DBA_TABLES  TO app_user;

Also note that if a user needs access to only a subset of tables in a schema, schema-level privileges are not appropriate — the user will gain access to the whole schema. In that case, continue using individual object grants or a curated role.


Summary

Schema privileges in Oracle 23ai fill a decade-long gap in Oracle's privilege model. The before and after is stark:

Scenario Before 23ai Oracle 23ai
Grant read access to all tables in hr 1 grant per table + maintain on every new table GRANT SELECT ANY TABLE ON SCHEMA hr TO user
New table added to hr Manual grant required Automatic — no action needed
Scope a system privilege to one schema Not possible ON SCHEMA schema_name clause
Avoid GRANT ANY security risk Choose: manual or insecure Schema-scoped — least privilege without maintenance overhead
Audit schema privilege grants DBA_SYS_PRIVS (flat list) DBA_SCHEMA_PRIVS (schema-aware)

For any Oracle 23ai environment running multi-schema applications with service accounts, replacing GRANT ANY with schema-scoped grants should be one of the first security improvements you make.