Security
How pg-smart-search prevents SQL Injection, manages parameterized queries, and secures filter keys for enterprise use.
Security
Search engines are notorious for SQL Injection vulnerabilities because they dynamically construct queries based on user input. pg-smart-search (v1.1+) is built with an enterprise-grade security model.
Parameterized Queries
All user inputs (search terms, filters, cursor values) are passed as parameterized query values. The engine never interpolates raw input into the SQL string.
SqlSanitizer & Identifier Whitelist
While values are parameterized, SQL identifiers (table names, column names) cannot be parameterized in PostgreSQL.
To prevent injection via identifiers, the engine uses a strict whitelist system (SqlSanitizer):
tableName,searchColumns,languageColumn,ftsColumn, andidColumnare checked against a safe-identifier pattern (letters, digits, underscores, no quotes/semicolons/ whitespace) at engine construction time.- This is a syntactic whitelist, not a schema check — the engine does not query
information_schemato confirm the table/columns actually exist. A safely-formatted but nonexistent column will pass this check and only fail later, as a normal Postgres error, when a query actually runs against it. - If a value fails the pattern (e.g. contains
;,', or whitespace), the sanitizer throws a fatalSqlInjectionErrorrather than building the query.
Never construct searchColumns from req.query directly. Always map user
input to a predefined array of allowed columns.
Injection-Safe Filter Keys
Filter keys (e.g. { category: "tech" }) go through the same identifier-whitelist check
as searchColumns above, applied at call time to whatever keys you pass in filters.
There is no pre-declared allowlist of permitted filter columns in the engine
config — any syntactically-safe identifier is accepted as a filter key. The protection is
against SQL injection via the key/value themselves (values are always bound as
parameters, keys must match the safe-identifier pattern), not against filtering on a
column you didn't intend to expose. If you're building filters from user input, map it
to an explicit allowlist yourself before passing it to search(), the same way you would
for searchColumns.