Database security checklist¶
A reference for the controls worth verifying before a database reaches production or undergoes a security review. Detail on each area is in the adjacent pages: access control, parameterised queries, input handling, configuration, and monitoring.
Access control¶
Application accounts connect with the minimum permissions they need: SELECT for read-only services,
SELECT, INSERT, UPDATE for write-capable ones, nothing broader. GRANT ALL is a development shortcut
that tends to persist into production.
Host restriction narrows the connection surface. app_user@'10.0.1.%' is meaningfully different from
app_user@'%': the first limits where the credential can be used even if it is compromised.
Each service connects with its own credentials. Shared accounts conflate audit trails and complicate independent credential rotation.
Existing permission grants accumulate. Periodic review with SHOW GRANTS (MySQL) or \du+ and
information_schema.role_table_grants (PostgreSQL) surfaces accounts carrying more access than their
current role requires.
Authentication and connections¶
TLS for all connections, including internal service-to-database links on private networks. Default database configurations often leave TLS optional rather than required.
Default credentials removed: MySQL root with no password, MongoDB with authentication disabled,
Redis with no password and bound to 0.0.0.0. These defaults have been the initial access point in
numerous real incidents.
Strong authentication mechanisms preferred: SCRAM-SHA-256 (PostgreSQL 14+ default) rather than older challenge-response methods; X.509 certificates for service-to-database connections where available.
Configuration¶
Features not in use are worth disabling rather than leaving as a latent attack surface:
MySQL
local_infile=0disablesLOAD DATA LOCAL INFILE, which can read arbitrary files from the client hostMongoDB
enableLocalhostAuthBypass: falseprevents unauthenticated localhost accessRedis bound to loopback or a private interface in
redis.conf; not0.0.0.0PostgreSQL
pg_read_file()accessible to superusers only; application accounts are not superusers
Development flags (DEBUG, interactive console access, verbose error output) are not active in
production.
Input handling¶
Parameterised queries throughout: no string concatenation into SQL. ORM raw query escape hatches
(.raw(), execute(), extra()) use the params argument rather than string interpolation.
Allowlist validation for fields with constrained formats (identifiers, codes, enums) before the value reaches the query layer. This catches structurally invalid input before parameterisation handles it.
NoSQL queries validate that user input is the expected type and structure before it appears as a query
operand. MongoDB $where evaluation (which runs JavaScript) is disabled by default since 4.4; older
deployments are worth checking.
Encryption at rest¶
Database files or volumes encrypted at the filesystem level (dm-crypt/LUKS on Linux, BitLocker on Windows, or cloud storage encryption). Relevant for physical media protection and misconfigured-backup scenarios; does not protect a running database with its decryption key loaded.
Audit logging¶
Failed authentication attempts logged and reviewed. Schema changes (DROP TABLE, ALTER TABLE,
GRANT) in production logged with alerting configured: these are rare in normal operation and
investigable when they appear.
Logs shipped to a system separate from the database server. Logs on the same system an attacker has compromised can be cleared; a centralised log aggregator (Elasticsearch, Splunk, Grafana Loki) preserves the record independently.
Backups¶
Backups verified by restoration test. A backup that has never been restored is an untested assumption.
Backup files encrypted and stored separately from the live database, with access controls that do not overlap with those on the database itself.