Table of Contents
8. Database Safety — Parameterization, Boundaries, and Least Privilege
A database is not just storage.
It is a boundary
— one of the most sensitive in the entire system.
Security comes from treating the database as a separate trust domain,
not an extension of application logic.
The goal is simple:
even if the application misbehaves,
the database should remain safe.
This page introduces the mental model for protecting data through parameterization, boundaries, and least privilege.
1. Parameterization Is Non‑Negotiable
SQL injection is not a theoretical risk.
It is one of the most common and most damaging vulnerabilities.
The only reliable defense is parameterized queries:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?'); $stmt->execute([$email]);
Parameterization:
- separates code from data
- prevents injection
- enforces type safety
- avoids string concatenation pitfalls
Escaping is not enough.
Sanitization is not enough.
Only parameterization is safe.
2. Never Build SQL by Concatenating Strings
Avoid patterns like:
$sql = "SELECT * FROM users WHERE email = '$email'";
Or:
$sql = "UPDATE posts SET title = '$title' WHERE id = $id";
Even if you “sanitize” the input, string‑built SQL is fragile and error‑prone.
A secure system never mixes:
- SQL structure
- untrusted data
in the same string.
3. Use Named Parameters for Clarity
Named parameters make queries more readable and less error‑prone:
$stmt = $pdo->prepare('SELECT * FROM posts WHERE author_id = :id'); $stmt->execute(['id' => $userId]);
This improves:
- clarity
- maintainability
- correctness
Named parameters are small, but they reduce entire classes of mistakes.
4. Avoid Dynamic SQL Whenever Possible
Dynamic SQL is dangerous because it changes the structure of the query based on input.
Avoid patterns like:
- dynamic ORDER BY
- dynamic column names
- dynamic table names
- dynamic WHERE clauses built from strings
If you must use dynamic SQL:
- whitelist values
- validate structure
- avoid passing raw input into SQL keywords
Dynamic SQL should be rare and carefully controlled.
5. Use the Least Privilege Principle for Database Users
Your application should not connect to the database as a superuser.
Prefer:
- one database user per application
- minimal privileges
- no ability to create tables
- no ability to drop tables
- no ability to modify schema
- no access to other databases
A compromised application should not become a compromised database.
6. Separate Read and Write Connections
For larger systems:
- use a read‑only user for SELECT queries
- use a write‑privileged user for INSERT/UPDATE/DELETE
This reduces:
- accidental writes
- injection damage
- privilege escalation
Read‑only connections are a powerful safety boundary.
7. Validate Data Before It Reaches the Database
The database is not a validator.
It is a boundary.
Validate:
- types
- shapes
- lengths
- formats
- domain rules
before the data reaches SQL.
The database should receive only predictable, shaped data.
8. Use Transactions for Consistency and Safety
Transactions protect:
- integrity
- consistency
- partial failures
- race conditions
Example:
$pdo->beginTransaction(); // operations $pdo->commit();
A secure system uses transactions not just for correctness, but for safety.
9. Avoid Exposing Raw Database Errors
Database errors can leak:
- table names
- column names
- SQL structure
- internal logic
- sensitive data
A secure system:
- logs detailed errors privately
- shows generic messages publicly
- avoids exposing SQL in exceptions
Error messages are a boundary.
10. Treat the Database as a Separate Security Domain
The database is not an extension of PHP.
It is a separate system with its own:
- permissions
- boundaries
- constraints
- risks
Security comes from respecting that separation.
Summary
Database safety is about boundaries, not cleverness.
A secure system:
- uses parameterized queries
- avoids string‑built SQL
- uses named parameters
- limits dynamic SQL
- applies least privilege
- separates read and write access
- validates data before SQL
- uses transactions
- hides internal errors
- treats the database as a separate trust domain
The database is where truth lives.
Protect it with intention.
This page describes secure defaults and common practices in modern PHP. It is not a complete security guide, and it does not replace formal audits or framework‑specific documentation. These principles are consistent enough to be useful, but security always requires context, judgment, and ongoing review.
