[{"data":1,"prerenderedAt":103},["ShallowReactive",2],{"qa-\u002Fsql\u002Fsecurity\u002Fpermissions":3},{"page":4,"siblings":95,"blog":100},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":90,"topic":91,"topicSlug":92,"updated":93,"__hash__":94},"qa\u002Fsql\u002Fsecurity\u002Fpermissions.md","Permissions",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","SQL","sql",{},true,1,"\u002Fsql\u002Fsecurity\u002Fpermissions",[23,28,32,36,40,45,49,53,57,61,65,69,73,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"grant-revoke","easy","What do GRANT and REVOKE do?","`GRANT` gives a database user or role permission to perform an operation.\n`REVOKE` removes a previously granted permission.\n\n```sql\n-- Grant SELECT on one table\nGRANT SELECT ON orders TO analyst_user;\n\n-- Grant multiple privileges at once\nGRANT SELECT, INSERT, UPDATE ON products TO app_user;\n\n-- Grant on all tables in a schema (Postgres)\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;\n\n-- Grant to a role (not a user directly)\nGRANT SELECT ON customers TO reporting_role;\n\n-- Revoke a specific privilege\nREVOKE INSERT ON products FROM app_user;\n\n-- Revoke all privileges on a table\nREVOKE ALL PRIVILEGES ON orders FROM analyst_user;\n```\n\n**Rule of thumb:** always grant to **roles**, not individual users. Assign\nusers to roles. This makes permission management scalable — add a new\nemployee by assigning them to the correct role, not by running a dozen\n`GRANT` statements.\n",{"id":29,"difficulty":25,"q":30,"a":31},"least-privilege","What is the principle of least privilege and how does it apply to SQL?","**Least privilege** means giving each user or service account only the\nminimum database permissions needed to perform its function — no more.\n\n```sql\n-- Application database user: only needs to read\u002Fwrite its own tables\nCREATE ROLE app_role;\nGRANT SELECT, INSERT, UPDATE, DELETE ON orders    TO app_role;\nGRANT SELECT, INSERT, UPDATE, DELETE ON customers TO app_role;\nGRANT SELECT ON products TO app_role;  -- read-only on the product catalogue\n-- NOT granted: DROP TABLE, CREATE TABLE, TRUNCATE, ALTER\n\n-- Reporting user: read-only access\nCREATE ROLE reporting_role;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting_role;\n\n-- Migrations user: only runs during deploys\nCREATE ROLE migration_role;\nGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO migration_role;\nGRANT CREATE ON SCHEMA public TO migration_role;\n```\n\n**Rule of thumb:** the application's runtime database user should never\nhave `DROP TABLE`, `TRUNCATE`, or `ALTER TABLE` privileges. Use a separate\nmigration user for schema changes, and revoke it after deploys.\n",{"id":33,"difficulty":14,"q":34,"a":35},"roles","What are roles and how do they differ from users?","A **role** is a named collection of privileges. A **user** is a role that\ncan log in. In Postgres, users and roles are unified — `CREATE USER` is\nsyntactic sugar for `CREATE ROLE … LOGIN`.\n\n```sql\n-- Postgres: create roles\nCREATE ROLE readonly_role;\nCREATE ROLE readwrite_role;\nCREATE ROLE admin_role;\n\n-- Grant privileges to roles\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite_role;\n\n-- Create users (roles that can log in)\nCREATE ROLE alice LOGIN PASSWORD 'secret';\nCREATE ROLE bob   LOGIN PASSWORD 'secret';\n\n-- Assign users to roles\nGRANT readonly_role  TO alice;\nGRANT readwrite_role TO bob;\n\n-- Alice now inherits all privileges of readonly_role\n```\n\nSQL Server uses `CREATE LOGIN` (server-level) + `CREATE USER` (database-level)\n+ `CREATE ROLE` as separate concepts.\n\n**Rule of thumb:** define a small set of roles that match your access\npatterns (read-only, read-write, admin, migration). Add users to roles\nrather than granting privileges to individual users — it is far easier to\naudit and maintain.\n",{"id":37,"difficulty":14,"q":38,"a":39},"grant-with-grant-option","What does WITH GRANT OPTION do?","`WITH GRANT OPTION` allows the grantee to re-grant the same privilege\nto other users or roles.\n\n```sql\n-- Alice can SELECT on orders AND can grant that to others\nGRANT SELECT ON orders TO alice WITH GRANT OPTION;\n\n-- Alice can now do:\nGRANT SELECT ON orders TO bob;  -- valid because alice has GRANT OPTION\n\n-- Revoke cascades to anyone alice granted to\nREVOKE SELECT ON orders FROM alice CASCADE;\n-- Bob loses SELECT too, because it came from alice\n```\n\n`WITH GRANT OPTION` creates a chain of trust that is hard to audit — Bob's\naccess depends on Alice's access, and revoking Alice's access removes Bob's.\n\n**Rule of thumb:** avoid `WITH GRANT OPTION` in most cases — it makes\npermission chains hard to audit and revocations unpredictable. Only use it\nfor schema owners or DBA roles that are explicitly responsible for managing\naccess.\n",{"id":41,"difficulty":42,"q":43,"a":44},"row-level-security","hard","What is Row-Level Security (RLS) in Postgres?","**Row-Level Security (RLS)** enforces access policies at the table level —\nthe database automatically filters rows based on the current user, hiding\nrows that the policy says the user cannot see.\n\n```sql\n-- Enable RLS on the table\nALTER TABLE orders ENABLE ROW LEVEL SECURITY;\n\n-- Policy: users can only see their own orders\nCREATE POLICY own_orders ON orders\n  FOR ALL\n  TO app_role\n  USING (customer_id = current_setting('app.current_user_id')::INT);\n\n-- Application sets the config before every query\nSET app.current_user_id = '42';\nSELECT * FROM orders;  -- automatically filtered to customer_id = 42\n\n-- Bypass RLS (table owner and superuser bypass by default)\nALTER TABLE orders FORCE ROW LEVEL SECURITY;  -- forces even for table owner\n```\n\n**Rule of thumb:** use RLS for multi-tenant applications where every table\nquery must be tenant-scoped. It enforces the isolation at the database level\n— a bug in application code cannot accidentally expose another tenant's data.\n",{"id":46,"difficulty":14,"q":47,"a":48},"schema-permissions","How do schema-level permissions work?","In Postgres, a user must have `USAGE` on a **schema** before they can access\nany objects within it, even if they have `SELECT` on individual tables.\n\n```sql\n-- Step 1: grant schema access\nGRANT USAGE ON SCHEMA reporting TO analyst_role;\n\n-- Step 2: grant table access within the schema\nGRANT SELECT ON ALL TABLES IN SCHEMA reporting TO analyst_role;\n\n-- Ensure future tables are also covered (Postgres)\nALTER DEFAULT PRIVILEGES IN SCHEMA reporting\n  GRANT SELECT ON TABLES TO analyst_role;\n\n-- Deny schema access entirely\nREVOKE USAGE ON SCHEMA private_data FROM analyst_role;\n-- Now analyst_role cannot see any tables in private_data schema\n```\n\n**Rule of thumb:** use separate schemas (`app`, `reporting`, `audit`,\n`staging`) and grant schema `USAGE` per role. This lets you grant broad\naccess to an entire schema with two `GRANT` statements instead of one per\ntable.\n",{"id":50,"difficulty":14,"q":51,"a":52},"column-level-grants","Can you grant permissions on specific columns rather than whole tables?","Yes — SQL supports column-level `SELECT`, `INSERT`, and `UPDATE` grants.\nThis lets you expose some columns of a sensitive table while hiding others\n(e.g., show names but not salaries).\n\n```sql\n-- Grant SELECT on specific columns only\nGRANT SELECT (id, name, department) ON employees TO hr_report_role;\n-- hr_report_role can NOT read salary or ssn columns\n\n-- Grant UPDATE on specific columns\nGRANT UPDATE (email, phone) ON users TO support_role;\n-- support_role can update contact info but not password_hash\n\n-- Revoke column-level grant\nREVOKE SELECT (salary) ON employees FROM payroll_role;\n```\n\nColumn-level grants work in Postgres, SQL Server, and MySQL but are\ncomplex to manage. An alternative is to create a **view** that exposes\nonly the allowed columns and grant `SELECT` on the view instead.\n\n**Rule of thumb:** prefer a view over column-level grants for hiding\nsensitive columns — views are easier to discover, test, and document.\nUse column-level grants when a view is not practical (e.g., you need\nwrite permissions on specific columns).\n",{"id":54,"difficulty":14,"q":55,"a":56},"superuser-privileges","What are superuser privileges and why should you avoid using them for applications?","A **superuser** (Postgres) or **sysadmin** (SQL Server) bypasses all\npermission checks and can do anything in the database — create\u002Fdrop\ndatabases, bypass RLS, read any table, impersonate other users.\n\n```sql\n-- Check if current user is a superuser (Postgres)\nSELECT current_user, usesuper FROM pg_user WHERE usename = current_user;\n\n-- Create a non-superuser admin for routine work\nCREATE ROLE dba_role CREATEDB CREATEROLE;  -- can manage DBs and roles, not superuser\n\n-- Application connection string should NEVER use a superuser\n-- BAD:  postgresql:\u002F\u002Fpostgres:password@host\u002Fdb\n-- GOOD: postgresql:\u002F\u002Fapp_user:password@host\u002Fdb (limited privileges)\n```\n\n**Rule of thumb:** the application's database connection string must never\nuse a superuser account. Use superuser credentials only for database\nadministration tasks, run from a secured bastion host or local machine —\nnever from application servers.\n",{"id":58,"difficulty":42,"q":59,"a":60},"audit-logging","How do you audit who changed what in a database?","Audit logging records which user performed which action and when. Common\napproaches:\n\n1. **Application-level**: record the actor and action in an audit table\n   from application code.\n2. **Trigger-based**: a database trigger automatically writes to an audit\n   table on every `INSERT`\u002F`UPDATE`\u002F`DELETE`.\n3. **Extension\u002Ffeature**: `pgaudit` (Postgres), SQL Server Audit, MySQL\n   General Log.\n\n```sql\n-- Postgres: trigger-based audit log\nCREATE TABLE audit_log (\n  id         BIGSERIAL PRIMARY KEY,\n  table_name TEXT NOT NULL,\n  operation  TEXT NOT NULL,  -- INSERT \u002F UPDATE \u002F DELETE\n  old_data   JSONB,\n  new_data   JSONB,\n  changed_by TEXT NOT NULL DEFAULT current_user,\n  changed_at TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$\nBEGIN\n  INSERT INTO audit_log (table_name, operation, old_data, new_data)\n  VALUES (TG_TABLE_NAME, TG_OP,\n          CASE WHEN TG_OP = 'DELETE' THEN row_to_json(OLD)::jsonb END,\n          CASE WHEN TG_OP \u003C> 'DELETE' THEN row_to_json(NEW)::jsonb END);\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER orders_audit\n  AFTER INSERT OR UPDATE OR DELETE ON orders\n  FOR EACH ROW EXECUTE FUNCTION audit_trigger();\n```\n\n**Rule of thumb:** trigger-based auditing is comprehensive but adds write\nlatency. For compliance-grade auditing, use a dedicated extension\n(`pgaudit`) or the database's native audit feature — they capture more\nevents (reads, DDL) and cannot be bypassed by application code that\ncircumvents triggers.\n",{"id":62,"difficulty":42,"q":63,"a":64},"revoking-public-schema","Why is the PUBLIC schema dangerous in Postgres and how do you secure it?","In Postgres, every user has `CREATE` and `USAGE` on the `public` schema by\ndefault (before Postgres 15). Any user can create tables there, potentially\n**shadowing** system functions or other users' objects via the search path.\n\n```sql\n-- Check who has what on the public schema\nSELECT grantee, privilege_type\nFROM   information_schema.role_schema_grants\nWHERE  schema_name = 'public';\n\n-- Revoke CREATE from all non-superusers (Postgres \u003C 15)\nREVOKE CREATE ON SCHEMA public FROM PUBLIC;\n\n-- Postgres 15+: CREATE is revoked from PUBLIC by default\n-- but USAGE is still granted — revoke if needed\nREVOKE USAGE ON SCHEMA public FROM PUBLIC;\n\n-- Application code should use an explicit schema, not rely on search_path\nSET search_path = app, public;\n-- Or set it per user:\nALTER ROLE app_user SET search_path = app;\n```\n\n**Rule of thumb:** on a shared database, immediately revoke `CREATE ON\nSCHEMA public FROM PUBLIC` (Postgres \u003C 15) and put application objects in\na dedicated schema. Set `search_path` explicitly for each role to prevent\nsearch-path hijacking attacks.\n",{"id":66,"difficulty":14,"q":67,"a":68},"password-policies","How do you manage database user passwords securely?","```sql\n-- Postgres: create a user with a password\nCREATE ROLE app_user LOGIN PASSWORD 'str0ng-p@ssw0rd!';\n\n-- Set password expiry (force rotation)\nALTER ROLE app_user VALID UNTIL '2026-12-31';\n\n-- Use SCRAM-SHA-256 authentication (more secure than md5)\n-- In pg_hba.conf: host all all 0.0.0.0\u002F0 scram-sha-256\n-- Then:\nSET password_encryption = 'scram-sha-256';\nALTER ROLE app_user PASSWORD 'new-password';\n\n-- MySQL: create user with strong auth plugin\nCREATE USER 'app_user'@'%' IDENTIFIED WITH caching_sha2_password BY 'str0ng-p@ss';\n\n-- SQL Server: enforce password policy (Windows policy integration)\nCREATE LOGIN app_login WITH PASSWORD = 'str0ng-p@ss!',\n  CHECK_POLICY = ON, CHECK_EXPIRATION = ON;\n```\n\n**Rule of thumb:** use randomly generated, long passwords (32+ characters)\nfor service accounts and store them in a secrets manager (Vault, AWS Secrets\nManager). Rotate passwords automatically. Never hardcode credentials in\napplication source code.\n",{"id":70,"difficulty":14,"q":71,"a":72},"connection-security","How do you restrict which hosts can connect to the database?","Database-level host restrictions add a network layer of access control —\neven if credentials are compromised, connections from unauthorised IPs are\nrejected.\n\n```sql\n-- Postgres: pg_hba.conf (host-based authentication file)\n-- Each line: TYPE  DATABASE  USER  ADDRESS  METHOD\n-- Allow the app server IP only:\nhost   myapp    app_user   10.0.1.5\u002F32    scram-sha-256\n-- Reject everything else:\nhost   myapp    all        0.0.0.0\u002F0      reject\n\n-- MySQL: user accounts include the host\nCREATE USER 'app_user'@'10.0.1.5' IDENTIFIED BY 'password';\n-- This account can ONLY connect from 10.0.1.5\nGRANT ALL ON myapp.* TO 'app_user'@'10.0.1.5';\n\n-- SQL Server: use firewall rules (Azure Portal \u002F Windows Firewall)\n-- plus Windows authentication or IP restrictions in network config\n```\n\n**Rule of thumb:** never expose the database port to the public internet.\nAllow connections only from application servers and VPN\u002Fbastion hosts,\nusing IP allowlists at both the database (`pg_hba.conf`) and network\n(firewall) levels.\n",{"id":74,"difficulty":14,"q":75,"a":76},"view-as-security","How can views be used to implement access control?","By granting access to a **view** instead of the base table, you restrict\nwhat data a user can see — without row-level security policies or column\ngrants.\n\n```sql\n-- Base table: all employees including salary and SSN\n-- View: only expose name, department, and hire date\nCREATE VIEW employee_directory AS\n  SELECT id, full_name, department, hire_date\n  FROM   employees\n  WHERE  terminated_at IS NULL;\n\n-- Grant SELECT on the view only\nGRANT SELECT ON employee_directory TO hr_partner_role;\nREVOKE ALL ON employees FROM hr_partner_role;  -- no direct table access\n\n-- HR partner can now run:\nSELECT * FROM employee_directory WHERE department = 'Engineering';\n-- Cannot see salary, SSN, or terminated employees\n```\n\n**Rule of thumb:** use views as a security layer for read-only access to\nsensitive tables when you want the constraint to be declarative and\nself-documenting. For write-access scenarios, combine views with `INSTEAD\nOF` triggers or handle mutations directly on the restricted columns.\n",{"id":78,"difficulty":14,"q":79,"a":80},"default-privileges","What are default privileges and why do they matter?","**Default privileges** define the permissions automatically applied to\nfuture objects (tables, sequences, functions) created in a schema. Without\nthem, a role that has `SELECT` on all current tables loses access as soon as\na new table is created.\n\n```sql\n-- Postgres: set default privileges for future tables in a schema\n-- Run this as the schema owner:\nALTER DEFAULT PRIVILEGES IN SCHEMA app\n  GRANT SELECT ON TABLES TO readonly_role;\n\nALTER DEFAULT PRIVILEGES IN SCHEMA app\n  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite_role;\n\nALTER DEFAULT PRIVILEGES IN SCHEMA app\n  GRANT USAGE, SELECT ON SEQUENCES TO readwrite_role;\n\n-- Verify current default privileges\nSELECT * FROM pg_default_acl;\n\n-- MySQL: no native default privileges; use a provisioning script or IAM\n```\n\n**Rule of thumb:** set `ALTER DEFAULT PRIVILEGES` for every role when you\nfirst create the schema. Without it, each new table deployed in a migration\nrequires a separate `GRANT` — easy to forget, causing silent 403s in\nproduction.\n",{"id":82,"difficulty":14,"q":83,"a":84},"privilege-inspection","How do you inspect what permissions a user or role currently has?","```sql\n-- Postgres: check table-level privileges\nSELECT grantee, table_name, privilege_type\nFROM   information_schema.role_table_grants\nWHERE  grantee = 'analyst_role'\nORDER  BY table_name;\n\n-- Postgres: psql shorthand\n-- \\dp orders           → show ACL for the orders table\n-- \\du analyst_role     → show role attributes and memberships\n-- \\z                   → show ACLs for all tables\n\n-- Postgres: check schema privileges\nSELECT grantee, schema_name, privilege_type\nFROM   information_schema.role_schema_grants\nWHERE  grantee = 'analyst_role';\n\n-- MySQL\nSHOW GRANTS FOR 'app_user'@'%';\n\n-- SQL Server\nSELECT principal_name, object_name, permission_name, state_desc\nFROM   sys.database_permissions dp\nJOIN   sys.database_principals  pr ON dp.grantee_principal_id = pr.principal_id\nWHERE  pr.name = 'analyst_role';\n```\n\n**Rule of thumb:** before revoking or changing permissions, always inspect\nthe current state first. In Postgres, `\\dp \u003Ctablename>` is the fastest\nway to spot unexpected `PUBLIC` grants on sensitive tables.\n",15,null,{"description":11},"SQL permissions interview questions — GRANT, REVOKE, roles, least-privilege, row-level security, column-level grants, schema ownership, and access control patterns across Postgres, MySQL, and SQL Server.","sql\u002Fsecurity\u002Fpermissions","Permissions & Roles","Security & Integrity","security","2026-06-20","HEpF1IbUqXS2me_x56LuGaV5k4-5e2wf_XpPYrAOBv8",[96,97],{"subtopic":90,"path":21,"order":20},{"subtopic":98,"path":99,"order":12},"SQL Injection","\u002Fsql\u002Fsecurity\u002Fsql-injection",{"path":101,"title":102},"\u002Fblog\u002Fsql-permissions-roles-grants","SQL Permissions & Roles — GRANT, REVOKE, and Least Privilege",1782244107530]