Parameterized query helpers for the local SQLite layer.
Several historical call sites built SQL by concatenating user input directly into the statement text, e.g.:
db.prepare(`SELECT * FROM users WHERE name LIKE '%${term}%'`).all();This is a classic SQL injection vulnerability: an input like
'; DROP TABLE users; -- changes the meaning of the query rather than acting
as data.
Always bind dynamic values as parameters (?) and never interpolate them into
the SQL text.
QueryBuilder— fluent builder that emits?placeholders for every value and validates table/column identifiers against a strict allow-list.UserRepository— data-access layer that uses bound parameters for all lookups, searches, and inserts.sanitize—escapeLike,stripControlChars, andsafeIdentifierfor the rare cases where a value cannot be bound (LIKE fragments, dynamic identifiers).
- Prefer bound parameters (
?) for every dynamic value. - Identifiers (table/column names) cannot be bound — validate them with
safeIdentifier/assertIdentifier. - Escape LIKE wildcards with
escapeLikeand pair withESCAPE '\'. - Never build SQL with template-literal interpolation of user input.
See test/lib/db/secure-query.test.ts for regression coverage.