skip to content

Access Control & Data Protection

I learn how a relational database decides who may connect and what each account may do to the data, and how the engine protects data it stores and transmits. Interviewers use this area to test whether I can design least-privilege access for a real application, not just write queries as a superuser.

part ofRelational database conceptsoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

What should a database audit trail record, and why is logging inside the application not considered sufficient for auditing database access?

level: juniorimportance: must knowfreq 52%

answer

  1. Who / what / which object / when / where / outcome
  2. Four buckets: logins, DDL, privileges, sensitive data
  3. Failures matter as much as successes
  4. App is inside the audited trust boundary
  5. Pool hides the end user — propagate identity

basics

~20 s

Record who, what, which object, when, from where, and the outcome: logins and failed logins, DDL, privilege changes, and DML or reads on sensitive tables. App logs miss DBAs, tools and jobs that connect directly, and can be bypassed.

solid answer

~50 s

An audit trail answers **who did what, to which object, when, from where, and did it succeed**. A practical baseline has four buckets: 1. **Authentication events** — successful and failed logins, client host. Cheap, high value. 2. **DDL** — CREATE/ALTER/DROP. Low volume; unexplained schema change is an incident. 3. **Privilege and role changes** — grants, role membership, ownership, password changes. This is how an attacker persists. 4. **Access to sensitive data** — DML, and reads where regulation requires it. Application logging alone fails for three reasons: the app is not the only client (DBAs, migrations, ETL, BI, break-glass, stolen credentials all bypass it); the app sits *inside* the trust boundary being audited, so its own logs are not independent evidence; and app logs record intent, not the statements that actually ran. One caveat: with a connection pool the engine sees only the pool's login, so propagate the end-user identity into the session.

go deeper

for a junior

Name the record shape (who/what/when/where/outcome) and the four buckets, and say auditing happens in the engine because not every client is your application.

for a middle

Add why the four buckets differ in volume-versus-value, why failures are logged, and the difference between an audit trail and the general query log.

for a senior

Bring the trust-boundary argument, the pooled-identity fix, correlation with application logs by request id, and the fact that audit output is itself sensitive data that must leave the host.

for a principal

Frame scope from the control objectives and regulatory scope rather than from what the engine can emit, and talk about who owns the audit pipeline versus who administers the database.

## What database auditing is Auditing is the practice of producing a durable, tamper-evident record of security-relevant activity inside the database engine, so that afterwards you can answer: **who** did **what**, to **which object**, **when**, **from where**, and **with what outcome**. It is routinely confused with three neighbours it is not: application logging (business events for product analytics and debugging), monitoring and metrics (aggregate health, no per-actor detail), and the general query log (a performance-debugging tool that is normally sampled, truncated, or switched off under load — exactly the moment an auditor cares about). ## Anatomy of an audit record A useful record carries, at minimum: - a timestamp with time zone plus an ordering key (sequence number or transaction id); - the session id, the authenticated database login, and the *effective* role if the session switched roles; - the end-user identity carried down from the application, when the app uses a shared pooled login; - client host/IP and the client program or application name; - the object touched — schema and table, and column set where the engine supports it; - the action class — LOGIN, LOGOUT, DDL, READ, WRITE, ROLE/privilege change; - the statement text and/or its bind parameters; - the **outcome**: success or failure, error code, rows affected where available. Outcome matters as much as success. Failed logins and permission-denied errors are the primary signal for credential stuffing and privilege probing; an audit design that logs only successful actions is blind to the reconnaissance phase of an attack. ## What belongs in scope A sensible baseline is four buckets, ordered by value-per-byte: 1. **Authentication events.** Volume is tiny (one record per connection), value is high. Failed-login bursts, logins from new hosts, and out-of-hours logins by human accounts are the classic detections. 2. **DDL.** CREATE/ALTER/DROP of tables, indexes, views, functions. Volume is near zero outside deploy windows. Every schema change that does not map to a known migration is an incident until proven otherwise. 3. **Privilege and role changes.** Grants, role membership, ownership transfers, password/credential changes. This is how an intruder converts a foothold into persistence, and how an insider quietly widens their own access. 4. **Access to sensitive data.** DML on regulated tables, and — where health, payment or personal-data rules demand it — *read* access too. This is the only bucket with serious volume, so it is the one to scope by object rather than turning on globally. ## Why application logging is not enough Three structural reasons, and interviewers want all three: **Bypass paths.** The application is not the only client of the database. DBAs with a SQL shell, schema-migration tools, ETL and reverse-ETL jobs, BI tools, break-glass accounts, restore-from-backup procedures, and an attacker holding stolen credentials all reach the data without passing through application code. Only the engine sits on every path. **Trust boundary.** If the subject of an investigation is the application itself — a bug, an injection, or an engineer who can deploy code — then logs emitted by that application are evidence produced by the accused. Engine-side auditing is generated by a different component, ideally administered by different people, which is what makes it usable as evidence. **Fidelity.** Application logs record *intent* ("user 42 updated their profile"). They do not record the statement that actually executed. An ORM cascade, a missing WHERE clause, or an injected predicate can touch far more rows than the intent implies. Engine auditing records what really happened. None of this makes application logs worthless — they carry business context the engine cannot know. The right answer is both, correlated by a request/trace id present in each. ## The connection-pool identity problem In practice almost every application connects through a pool using a single database login, so engine audit records show `app_user` for every action and attribution stops there. The fix is to push the end-user identity into the session at checkout: set a session context variable or the client application name per request, or switch to a per-tenant role for the duration of the request, so that the engine's own records carry the human identity. Without it, you are stuck joining engine records to application records by timestamp — fragile, and useless under concurrency. Mentioning this detail signals you have actually operated an audited system rather than read about one. ## Handling and retention Audit output should leave the database host quickly for a collector or SIEM, be append-only where it lands, and carry a retention period long enough for the applicable regulation but not indefinite — statement text and parameters frequently contain personal data, so the audit stream inherits the sensitivity of the data it describes and becomes a target in its own right. ## How to pitch the answer Lead with the record shape (who/what/which/when/where/outcome), give the four buckets, then make the bypass-and-trust-boundary argument for auditing at the engine, and close with the pooled-identity caveat.

  • Your application connects through a pool as a single database login. How do you get the end-user identity into the engine's audit records?
    Set a per-request session attribute at pool checkout — a session context variable, GUC, or the client application_name — carrying the authenticated end-user id and a request/trace id, and reset it on return so identities never leak between borrowers. Where roles are per-tenant, SET ROLE for the request instead, which also tightens authorization. If the engine's audit format cannot carry it, log a correlation id in both the engine record and the application log and join on that.
  • Why audit failed statements and failed logins, not just successful ones?
    Failures are the reconnaissance signal. Credential stuffing shows up as bursts of failed logins from few hosts; privilege probing shows up as permission-denied errors on tables an account has no business touching. An audit trail of successes only tells you what an attacker achieved, never that they were trying, so detection arrives after the damage instead of before.
  • How is an audit log different from the general query log the DBAs already have?
    The query log exists for performance debugging: it is often sampled, truncated, rotated aggressively, disabled under load, and freely readable and deletable by the same administrators it would incriminate. An audit log has defined scope, guaranteed capture for in-scope events, an integrity and retention policy, and separation of duties over its storage. They can share plumbing, but a query log is not evidence.

Application logs are the shop's own sales notes; engine auditing is the CCTV on the door. If you suspect the shopkeeper, you want the camera, not their notebook.

saying these in an interview costs you the question

  • "We log everything in the application, so the database is covered" — ignores DBAs, migrations, ETL and stolen-credential access.
  • Auditing only successful operations and ignoring failed logins and permission-denied errors.
  • Treating the engine's general query/slow log as an audit trail.
  • Accepting `app_user` as the actor because the pool hides the human identity.
  • Believing auditing enforces anything — it is detective, not preventive, control.

context

open as a page

In a relational database, what is the difference between authentication and authorization, and at what point in a session is each one performed?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Authentication proves who is connecting. It happens once, during the connection handshake, using a password, an OS identity, a Kerberos ticket, or a client certificate. Authorization decides what that identity may do, and the server re-checks it on every statement afterwards.

open as a page

What is dynamic data masking in a relational database — where the engine rewrites column values as they are read — and how does it differ from storing the data already redacted or tokenised?

level: juniorimportance: must knowfreq 50%

basics

~20 s

Dynamic masking keeps the true value stored and applies a masking function at query time based on who is asking, so privileged roles still see the original. Write-time redaction or tokenisation changes what is stored, so the original cannot leak from that table at all.

open as a page

At which layers can a relational database's data be encrypted at rest — storage volume or filesystem, database engine, and application or column level — and what fundamentally differs between them?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Volume or filesystem encryption protects whole disks and is invisible to the database. Engine-level Transparent Data Encryption encrypts the database's own files and backups. Application or column encryption encrypts values before storage, so even the database never sees plaintext — but indexing and range queries break.

open as a page

When a client application connects to a relational database over TLS, what exactly is protected, and which threats does that encryption not address?

level: juniorimportance: must knowfreq 55%

basics

~20 s

TLS protects the wire: credentials, SQL text, parameters and result rows cannot be read or silently altered by anyone on the network path, and the client can verify it reached the real server. It does nothing for data on disk, backups, in-database authorization, or a compromised host.

open as a page

Why shouldn't a running application connect to its relational database as a superuser or as the owner of the tables it queries, and what kind of account would you give it instead?

level: juniorimportance: must knowfreq 68%

basics

~20 s

A superuser or table owner can drop and alter objects, read everything, and often bypass row-level rules. The application only needs SELECT/INSERT/UPDATE/DELETE on its own tables, so it should connect as a dedicated non-owner account holding exactly those grants. That caps the damage when credentials leak or a query is abused.

open as a page

In a relational database, what is the difference between a user and a role, and why do teams attach privileges to roles instead of granting them directly to each user?

level: juniorimportance: must knowfreq 58%

basics

~20 s

A user is an identity that can log in; a role is a named collection of privileges that can be granted to users or to other roles. Attaching privileges to roles means access is defined once per job function, and adding or removing a person is a single membership change instead of dozens of grants.

open as a page

A reporting role must read the customer table but must never see the ssn and salary columns. Compare enforcing that with column-level SELECT privileges versus exposing only a view that projects the allowed columns.

level: middleimportance: must knowfreq 55%

basics

~20 s

Column-level SELECT grants let the role query the real table but error on forbidden columns. A view revokes table access entirely and exposes a fixed projection. Grants are precise with nothing extra to maintain; views are flexible, tool-friendly and hide new columns by default.

open as a page

Transparent Data Encryption encrypts a database's files on disk without changing queries. Which attacks does it genuinely stop, which does it not, and which files besides the main datafiles must be covered?

level: middleimportance: must knowfreq 62%

basics

~20 s

It stops anyone who obtains the files without the key: stolen disks, copied datafiles, leaked snapshots and physical backups. It stops nothing arriving through an authenticated session — injection, stolen credentials, over-broad privileges. Coverage must include redo/WAL logs, temp files, replicas and backups.

open as a page

Database drivers expose TLS connection modes such as 'require', 'verify-ca' and 'verify-full'. What does each one actually check, and which of them stop an attacker who can intercept the connection?

level: middleimportance: must knowfreq 55%

basics

~20 s

'require' encrypts but validates nothing, so any impostor with any certificate is accepted. 'verify-ca' checks the certificate chains to a trusted CA but not the hostname. 'verify-full' checks the chain and that the certificate names the host you dialled. Only verify-full stops a man-in-the-middle.

open as a page

A service account was granted SELECT on a table but still gets 'permission denied' when it queries it. Which layers of privilege does a relational engine check for a simple table read, and how do those layers combine?

level: middleimportance: must knowfreq 55%

basics

~20 s

Privileges are checked per object touched, and they are containment-layered: the role needs access to the database, usage of the schema that holds the table, and SELECT on the table itself (plus any column-level restriction). Missing the schema-level privilege denies the query even though the table grant exists.

open as a page

Describe how you would separate the database account used by schema migrations from the account the application uses at runtime, and what each one is allowed to do.

level: middleimportance: must knowfreq 52%

basics

~20 s

The migration account owns the schema and holds DDL rights; it is used only by the deploy step and its credentials are not in the application's environment. The runtime account owns nothing and holds only DML on the tables the app touches. Migrations also issue the grants that keep the runtime account current.

open as a page

What is the difference between object privileges and system-level privileges in a relational database, and why does the distinction matter when you design access control?

level: middleimportance: must knowfreq 48%

basics

~20 s

Object privileges permit actions on a specific named object - SELECT on a table, EXECUTE on a function. System privileges permit actions on the database itself or on whole classes of object - creating tables, creating roles, connecting, bypassing policies. Object privileges are narrow and enumerable; system privileges are the escalation-prone ones.

open as a page

In a row-security policy definition, what is the difference between the USING predicate and the WITH CHECK predicate, and what goes wrong if you specify only USING?

level: middleimportance: must knowfreq 44%

basics

~20 s

USING filters rows that already exist — what SELECT returns and what UPDATE or DELETE may match. WITH CHECK validates row values being written by INSERT or produced by UPDATE. With only USING, a session can insert or update rows into another tenant, writing data it cannot then read.

open as a page

What is row-level security in a relational database, and when would you enforce tenant filtering with database row policies instead of a WHERE clause in application queries?

level: middleimportance: must knowfreq 58%

basics

~20 s

Row-level security attaches a boolean predicate to a table so a session can only see or modify rows the predicate accepts. The engine applies it to every statement, so a forgotten tenant filter in application code cannot leak another tenant's rows.

open as a page

Why is a column whose values are masked on read not equivalent to a column the user is not authorized to read? Describe concretely how someone querying a table with masked values can still learn the underlying data.

level: seniorimportance: must knowfreq 45%

basics

~20 s

Masking changes what is returned, not what the engine computes on. Predicates, joins, ORDER BY and aggregates still evaluate against the raw value, so a user can confirm guesses, binary-search ranges and rank rows. Real authorization means the value never participates in the query at all.

open as a page

What stops working when an application encrypts a column's values before storing them in a relational database, and how do teams work around each limitation?

level: seniorimportance: must knowfreq 48%

basics

~20 s

The engine can no longer interpret the value: range queries, sorting, pattern search, joins, uniqueness and server-side functions break, and values grow. Deterministic encryption restores equality lookups but leaks which rows are equal; keyed hashes give searchable blind indexes; ranges move into coarse buckets or the application.

open as a page

How are encryption keys organised and rotated for a database encrypted at rest, and what does envelope encryption — a data key wrapped by a key-encrypting key — buy you?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Data is encrypted with a data key; the data key is encrypted by a master key held in a key manager or HSM. Rotating the master key only re-wraps the data key — seconds. Rotating the data key means re-encrypting all data. Keep old key versions or old backups become unrestorable.

open as a page

What does adding WITH GRANT OPTION to a privilege change, and what happens to the privileges that grantee handed out when you later revoke theirs?

level: seniorimportance: must knowfreq 45%

basics

~20 s

WITH GRANT OPTION lets the grantee re-grant the privilege to others, creating a dependency graph of grants. Revoking the grantor's privilege must deal with those dependants: RESTRICT (the standard default) refuses while dependants exist, CASCADE removes them too. Also, you can only revoke grants you yourself made.

open as a page

When application servers share pooled database connections, how do you tell the database which tenant the current request belongs to so row policies can use it, and what can go wrong with that mechanism?

level: seniorimportance: must knowfreq 40%

basics

~20 s

Set a namespaced session variable at the start of each transaction (a set_config-style call read back by current_setting in the policy), scoped to the transaction so it disappears on commit or rollback. Danger: a value left on a pooled connection is inherited by the next request's tenant.

open as a page

What does it mean to grant a privilege to PUBLIC in a relational database, and why can revoking a privilege from a specific user still leave that user able to run the query?

level: juniorimportance: should knowfreq 35%

basics

~20 s

PUBLIC is a pseudo-role meaning every role in the database, including ones created later. A grant to PUBLIC is a separate, additive source of privilege, so revoking from an individual removes only that individual's grant while the PUBLIC one still applies to everyone.

open as a page

Database audit facilities usually let you configure statement-level auditing (capture every statement in a class, such as all DDL or all reads) or object-level auditing (capture only access to named tables or columns). Compare the two and describe when you would use each.

level: middleimportance: should knowfreq 34%

basics

~20 s

Statement-level captures a whole class of statements regardless of target — complete but high volume. Object-level captures only access to named tables or columns — cheap and focused, but blind to anything outside the list. Typical policy: statement-level for DDL, role and privileged sessions; object-level for sensitive tables.

open as a page

Many databases match an incoming connection against an ordered host-based access file (PostgreSQL's pg_hba.conf being the classic example) before any credential is checked. How does that matching work, and what mistakes does it commonly cause?

level: middleimportance: should knowfreq 40%

basics

~20 s

Each line matches a connection by type (local/TCP/TLS), target database, requested role and source address, and names an authentication method. The server scans top to bottom and uses the first matching line only. If that method fails, the connection is rejected — it never falls through to later lines.

open as a page

When a database authenticates a client with a password using SCRAM-SHA-256 challenge-response, what does that protocol give you that sending the password over the connection (or sending a simple MD5 hash of it) does not?

level: middleimportance: should knowfreq 45%

basics

~20 s

The password never crosses the wire in any replayable form. Both sides prove knowledge over a random per-session nonce, the server also proves itself to the client, and the stored verifier is a salted, iterated derivative that an attacker who steals it cannot directly replay to log in.

open as a page

A login account is a member of role A, which is itself a member of role B, and B holds SELECT on a table. How does the engine decide whether that login may read the table, and could a later grant ever take access away?

level: middleimportance: should knowfreq 40%

basics

~20 s

Effective privileges are the union of what the account holds directly, everything reachable transitively through role membership, and everything granted to PUBLIC. Standard SQL grants are additive with no deny, so a new grant can never remove access -- only REVOKE or dropping a membership can.

open as a page

How would you set up a database account for analysts and dashboards that need to query production data, so their queries cannot damage or disrupt the transactional workload?

level: middleimportance: should knowfreq 44%

basics

~20 s

Create a role with SELECT only - no DML, no DDL - on the specific tables or views it may see, point it at a read replica where possible, and attach per-account limits: read-only sessions, statement timeouts, and a capped connection allowance so a runaway query cannot starve the application.

open as a page

When one database role is granted membership in another, do the member's sessions automatically get the granted role's privileges? Explain how role inheritance and role activation work.

level: middleimportance: should knowfreq 40%

basics

~20 s

Not always. With inheritance, a member automatically uses the granted role's privileges in every session. Without it, membership only grants the right to switch into that role explicitly before its privileges apply. Engines differ: PostgreSQL roles are inheriting by default unless created NOINHERIT; Oracle distinguishes default roles from ones that must be enabled.

open as a page

Many relational databases have a built-in PUBLIC pseudo-role. What is it, what does it typically hold by default, and what would you do about it when hardening a database?

level: middleimportance: should knowfreq 36%

basics

~20 s

PUBLIC is an implicit group that every role belongs to and cannot be removed from, so anything granted to it is granted to everyone. Engines ship default PUBLIC grants - historically schema creation and access, connect rights, execute on many functions - so hardening starts by revoking those and granting explicitly instead.

open as a page

Database auditing exists largely to hold privileged users accountable — yet those same users administer the database that produces the audit records. How do you design an audit trail they cannot quietly edit or switch off?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Get the records off the database host fast, to append-only storage administered by someone else. Split duties so the DBA cannot change audit config or delete audit storage, audit the audit configuration itself, hash-chain or sign records, and alert on gaps, sequence breaks and the audit service stopping.

open as a page

You need a durable trail of who changed which rows in a set of business tables. Compare writing audit rows from AFTER-row triggers against building the trail from the database's transaction log with change data capture, and say when each is the right choice.

level: seniorimportance: should knowfreq 44%

basics

~20 s

Triggers write history inside the transaction: atomic, can capture the app user from session context, but they double writes, must be maintained per table, miss reads and bulk paths, and vanish on rollback. Log-based capture is off the hot path and catches every committed change, but records only committed data changes with weak actor attribution.

open as a page

showing 1–30 of 45