How to Check PostgreSQL privileges Correctly: A Guide to \dp Output

PostgreSQL exposes object-level permissions through Access Control Lists (ACLs). While the SQL GRANT and REVOKE commands are familiar to most administrators, the internal ACL representation—especially when viewed through psql’s \dp command—can be confusing at first glance.

One of the most common and misunderstood patterns looks like this:

postgres=arwdDxt/postgres+
=r/postgres

This article explains exactly what this means, how PostgreSQL stores and displays object privileges, and why this pattern has important security implications.


What \dp Shows

The \dp command (short for describe privileges) lists object-level access privileges for relations in the current database, including:

  • tables
  • views
  • materialized views
  • sequences
  • foreign tables

Unlike \\du (roles) or \\l (databases), \\dp operates at the data object layer—the final gate before a user can read or modify data.


Why the Output Appears on Multiple Lines

The + symbol at the end of a privilege line indicates that the ACL entry is continued on the next line for formatting reasons.

So this:

postgres=arwdDxt/postgres+
=r/postgres

Is not two separate rules. Internally, PostgreSQL stores this as a single ACL list, equivalent to:

postgres=arwdDxt/postgres,=r/postgres

Understanding this point is crucial to reading \\dp output correctly.


The PostgreSQL ACL Format

Every privilege entry follows this pattern:

See also: Mastering the Linux Command Line — Your Complete Free Training Guide

grantee=privileges/grantor

Multiple entries are separated by commas.

Example:

role1=privs/role2,role3=privs/role4

Let’s break down each part of the ACL string in our example.


Part 1: postgres=arwdDxt/postgres

Grantee

  • postgres
  • The role receiving the privileges

Privileges

  • arwdDxt

For tables and views, these letters mean:

LetterPrivilege
aINSERT
rSELECT
wUPDATE
dDELETE
DTRUNCATE
xREFERENCES
tTRIGGER

This represents full access to the object.

Grantor

  • postgres
  • The role that issued the grant

Interpretation

The role postgres has full control over this object.

This is expected, as postgres is typically the object owner.


Part 2: =r/postgres — The Critical Detail

This entry is subtle but extremely important.

Empty Grantee

Notice there is no role name before the =:

=r/postgres

In PostgreSQL ACL syntax:

An empty grantee means PUBLIC

PUBLIC is a special pseudo-role representing all users, including:

  • existing roles
  • roles created in the future

Privilege

  • r → SELECT

Grantor

  • postgres

What =r/postgres Really Means

This entry is equivalent to the SQL command:

GRANT SELECT ON object TO PUBLIC;

In plain language:

Every role that can connect to this database is allowed to read this object.

This includes:

  • application users
  • reporting users
  • maintenance accounts
  • any future roles with LOGIN and CONNECT

The Combined Meaning

Putting both ACL entries together:

postgres=arwdDxt/postgres,=r/postgres

The effective access policy is:

  1. postgres has full privileges (read/write/admin)
  2. All users (PUBLIC) have read-only (SELECT) access

Why This Pattern Is Common

This ACL structure appears frequently in:

  • system views (pg_stat_statements, pg_buffercache)
  • monitoring extensions
  • shared reporting tables

PostgreSQL extensions often grant SELECT to PUBLIC intentionally so that monitoring roles can query them without elevated privileges.


Security Implications

While common, this pattern can be dangerous if applied unintentionally.

Common Misinterpretation

Many administrators see:

postgres=arwdDxt/postgres

and assume:

“Only postgres can access this table.”

But the second line:

=r/postgres

silently grants access to everyone.

This is one of the most frequent causes of unexpected data exposure in PostgreSQL environments.


How to Verify Effective Access

To confirm whether a user can actually read the object:

SELECT has_table_privilege('some_user', 'schema.object', 'SELECT');

This function evaluates:

  • direct grants
  • role membership
  • PUBLIC privileges

How to Remove PUBLIC Access

To revoke the implicit read access:

REVOKE SELECT ON schema.object FROM PUBLIC;

After this, only explicitly granted roles (or the owner) can read the object.


How This Fits into PostgreSQL’s Security Model

A user must pass three separate checks to read data:

  1. LOGIN Can the role authenticate?
  2. CONNECT Can the role connect to the database?
  3. Object privilege (\\dp) Can the role SELECT this object?

The ACL shown by \\dp governs the final and most granular layer.


Mental Model

  • \\du → who can log in
  • \\l → who can enter the database
  • \\dp → who can read or modify specific data

The line:

=r/postgres

means:

“Once you’re inside, the door to this object is open to everyone.”


Conclusion

The ACL pattern:

postgres=arwdDxt/postgres+
=r/postgres

Does not mean exclusive access by postgres.

It means:

  • the owner has full control
  • all users have read access

Understanding this distinction is essential for accurate security audits and preventing accidental data exposure.

David Cao
David Cao

David is a Cloud & DevOps Enthusiast. He has years of experience as a Linux engineer. He had working experience in AMD, EMC. He likes Linux, Python, bash, and more. He is a technical blogger and a Software Engineer. He enjoys sharing his learning and contributing to open-source.

Articles: 577

Leave a Reply

Your email address will not be published. Required fields are marked *