SQL Security: Roles and Privileges

1. Hardware-Mirror: The Permission Cache
In 2026, the database engine does not check your permissions by reading from the disk on every query. That would be too slow. Instead, it uses the Permission Cache.
The ACL Physics
When a role (like sales_rep) is created, its Access Control List (ACL) is stored in the pg_authid and pg_class catalogs.
- The Mirror: Every time a session starts, these ACLs are loaded into the server's process-local memory.
- The Result: A permission check takes less than 1 microsecond. However, this means that if you
REVOKEa permission, it might take a few milliseconds for active sessions to "See" the change, as the engine must invalidate the local cache.
Senior Standard: Use nologin roles for logical groups and login roles for human or service identities. This separation ensures that your "Hardcoded" permissions are attached to the group, making it easy to rotate individual users without re-writing your entire security policy.
2. Role Hierarchies: The "Group" Architecture
In a company of 5,000 developers, you don't grant permissions to individuals. That is an administrative suicide. You build a Tree of Roles.
The Professional Hierarchy
- Grantor Roles: The roles that own the data.
- Access Roles:
read_only,read_write,admin_access. - Team Roles: Roles that map to your Slack/Teams groups (e.g.,
marketing_grp). - Login Roles: The actual credentials used by humans or machines.
Architect's Standard: If the analytics service needs write access later, you don't change the user; you simply GRANT data_writer TO marketing_grp. The security configuration lives in the Logic of the Group, not the Persistence of the User.
3. Row-Level Security (RLS): The Invisible Shield
Standard permissions are "Table-Level" (You see everything or nothing). RLS allows you to define permissions per-row. This is the ultimate tool for Multi-Tenancy.
Policy Rewriting Physics
When you enable RLS, the database engine Rewrites your query at the parser stage, before it reaches the Query Planner.
- User Query:
SELECT * FROM sales_data; - Database Rewrite:
SELECT * FROM sales_data WHERE owner_id = current_user_id(); - The Fence: The user doesn't see this
WHEREclause, and they cannot remove it. Even if they write their ownWHERE 1=1, the engine appends the security filter with a logical AND.
The LEAKPROOF Mirror
In high-security environments, you can mark RLS functions as LEAKPROOF. This tells the engine that the function is so secure that it can't be used to "Probe" data via side-channel timing attacks or clever error messages.
4. The Auditing Mirror: Building a Tape-Proof History
Security isn't just about Locking; it's about Proof. If a record is changed, you must know who changed it and what the old value was.
The "Trigger" Physics
The most robust auditing uses Row-Level Triggers that write changes to an audit_log table in a separate, restricted schema.
- The Mirror: The audit table should be Append-Only. Even the
api_usershould only haveINSERTaccess to the audit table, neverDELETEorUPDATE. - The Metadata: Always capture the
session_user, theclient_addr(IP address), and thequerytext.
pgAudit: Enterprise Standards
For high-compliance industries (FinTech, Health), we use the pgaudit extension. It hooks directly into the database logging engine to record every statement, including failed attempts, providing a tamper-proof record that satisfies SOC2 and HIPAA audits.
5. Connection Hardening: pg_hba.conf logic
Security starts at the Door. The pg_hba.conf (Host-Based Authentication) file is your gatekeeper.
The Mirror Configuration
- Local Trust: Only allow the
postgresuser to connect via local Unix sockets, never the network. - Scram-SHA-256: Only use modern, high-entropy password hashing. Never use
md5orpasswordmodes. - IP Whitelisting: Explicitly list the IP addresses of your App Servers. Reject everything else (
0.0.0.0/0is a failure of architecture). - SSL/TLS Mandatory: Force
hostsslconnections. Data moving between your App and DB must be encrypted to prevent "Packet Sniffing" within your private network.
6. SQL Injection: The Parser Breach
The #1 security threat to databases is SQL Injection. This isn't a database bug; it's a failure to understand How the Engine Reads.
The Logic Split
When you send a query like SELECT * FROM users WHERE id = '1' OR '1'='1', you are attempting to inject logic into the data stream.
- The Physics of Prepared Statements: By using Parameterized Queries, the database parses the SQL structure before looking at the data.
- The Parser Lock: Once the engine has decided that the query is an index scan on the
idcolumn, it "Locks in" that logic. Even if you supply a string that contains aDROP TABLEcommand, the engine treats it purely as a Literal Identifier. It tries to find a user whose ID is the literal string "DROP TABLE...". - The Result: The malicious code can never be executed because it arrived after the "Logical Branch" was already decided.
7. The Principle of Least Privilege: Granular Grants
In a high-fidelity system, your application user (e.g., web_app_user) should not own the tables. It should be a guest in the database.
The Granular Grant Mirror
- Table Level:
GRANT SELECT, INSERT ON orders TO web_app_user;(The app can read and write, but not delete or alter). - Column Level:
GRANT SELECT (id, status) ON orders TO support_user;(This user can see order statuses but is physically blocked from seeing customer credit card numbers). - Sequence Level: Remember to
GRANT USAGE ON SEQUENCE...or yourINSERTcommands will fail. - Function Level: Instead of granting access to a table, you can grant access to a Security Definer Function. The user can run
SELECT buy_product(), but they cannot read theproductstable directly. This creates a "Gated Execution" model.
6. Summary: The Hardening Checklist
- Least Privilege: The daily
app_usershould only haveSELECT/INSERT/UPDATEon specific tables. It should NEVER haveDROP TABLEorTRUNCATEprivileges. - Schema Isolation: Keep your raw data in a
privateschema and expose it through Views in thepublicschema. This adds a layer of abstraction between the user and the storage. - Ownership Mirror: Ensure your tables are owned by a
schema_ownerrole, not thepostgressuperuser. - Audit Fidelity: Log every change. In 2026, storage is cheap—Blindness is fatal.
- Revoke Public: By default, SQL allows any user to connect to the
publicschema. ExecuteREVOKE ALL ON SCHEMA public FROM PUBLIC;on day one into your migration scripts to start with a clean slate.
The Audit Integrity Mirror
In a high-security environment, your audit logs are useless if an attacker can delete them. To build a "Hardware-Mirror" level defense, you must ensure the immutability of the trail.
- The Immutability Physics: Store your audit logs in a separate, dedicated database or a Write-Once-Read-Many (WORM) storage device.
- The Checksum Mirror: Generate a cryptographic hash (SHA-256) of each audit log entry, including the unique hash of the previous log entry in the set. This creates a "Hash Chain" (effectively a centralized blockchain). If an attacker manages to modify or delete a single log entry, the entire chain's signature will fail to validate, providing immediate proof of tampering.
- The Senior Recommendation: For multi-region architectures, stream your audit logs to a global logging service (like CloudWatch or Datadog) using a native database collector. This keeps the logs outside the blast radius of the primary database server.
Security is the "Foundation of Trust." By mastering the Role hierarchy and the magic of Row-Level Isolation, you gain the ability to build systems where data integrity is guaranteed by math and physics, not just hoping your developers don't make mistakes. You graduate from "Managing a database" to "Architecting a Fortress."
Phase 21: Security Mastery Action Items
- Run
\dpin your terminal to see the Access Privileges of every table in your schema. - Implement a Role Hierarchy where a
managerrole inherits from aviewerrole. - Enable
ROW LEVEL SECURITYon acustomerstable and verify that different users can see only their own data. - Review your
pg_hba.confand ensure all remote connections are forced over SSL.
Read next: SQL Injection: Preventing the Parsing Breach →
Part of the SQL Mastery Course — engineering the defense.
