Secure Database Access

Advanced
11 min

Secure Database Access

The database holds everything worth stealing, and the SQL injection lesson covered only the most famous way in. There are quieter routes: an application account with administrator rights, a connection string in a public repository, a port open to the internet, a backup on an unencrypted disk. This lesson covers the layers around the query.

In this lesson you will learn to give the application the minimum privileges it needs, protect the connection and credentials, keep queries and models from becoming injection paths, and secure data at rest and in backups.

Least Privilege for the Application Account

The application should not connect as the database superuser. Create a dedicated user whose grants match what the code actually does, and consider separate users for read-heavy and write paths so a compromised reporting endpoint cannot modify data.

sql
-- PostgreSQL: an application role with data access only, no schema changes CREATE ROLE shop_app LOGIN PASSWORD 'from-secrets-manager'; GRANT CONNECT ON DATABASE shop TO shop_app; GRANT USAGE ON SCHEMA public TO shop_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO shop_app; -- Migrations run under a different, more privileged role from CI, never from the app

For multi-tenant systems, PostgreSQL row-level security can enforce the tenant filter in the database itself, so a forgotten WHERE tenant_id = ? in application code is not fatal. MongoDB offers the same idea through per-database users and roles such as readWrite scoped to one database.

Protecting the Connection

  • Network placement. The database listens on a private network only; the application reaches it through a security group or firewall rule, never through a public port.
  • TLS. Enable encrypted connections and verify the server certificate, so credentials and data do not cross the network in clear text.
  • Credentials. The connection string lives in a secrets manager and is injected as an environment variable at runtime. Rotate it on a schedule and immediately after anyone with access leaves the team.
  • Timeouts and pool limits. Bounded pools and connection timeouts keep one misbehaving endpoint from exhausting the database for everyone.

Query and Model Hygiene

Parameterized queries stop SQL injection; the equivalent discipline for document databases and ORMs is to keep user input out of the structure of the query:

javascript
// Vulnerable: an object in req.query becomes a query operator await User.find({ email: req.query.email }); // ?email[$ne]= matches everyone
javascript
// Safe: cast to the expected type, with sanitizeFilter as a backstop await User.find({ email: String(req.query.email) });

The sample schema adds three model-level defenses: strict: "throw" rejects unknown fields, immutable: true on role prevents privilege changes through ordinary updates, and select: false keeps the password hash out of every query unless explicitly requested. With SQL ORMs, the same rules apply: never pass raw user objects to update, and treat any "raw query" helper as a place where parameterization is your responsibility.

Data at Rest and Backups

Encryption at rest (managed disks, or the database engine's own feature) protects against a stolen disk or snapshot, and is usually one setting on a cloud provider. It does not protect against a compromised application account, which is why the least-privilege section matters more than most teams expect.

Backups are copies of your most sensitive data and deserve the same controls: encrypted, stored in a separate account, with access limited to the backup process and a small restore group. Test a restore regularly; an untested backup is a hope, not a control. Finally, enable the database's audit log for privileged operations (schema changes, grants, bulk exports) and alert on events that should never happen, such as the application role attempting DROP TABLE.

Quick Quiz
Question 1 of 2

Why should database migrations run under a different role than the application?

Key Takeaways

  • Connect as a least-privilege application role; run migrations from a separate, more privileged role.
  • Keep the database on a private network, require TLS, and load credentials from a secrets manager with rotation.
  • Cast user input to expected types, enable sanitizeFilter, and use strict, immutable, select: false model settings.
  • Encrypt data at rest and treat backups as sensitive: encrypted, isolated, access-limited, restore-tested.
  • Audit privileged database operations and alert on the ones that should never occur.

Next lesson: Secrets Management: Environment Variables, Vaults and Rotation — keeping the keys to everything out of code and logs.