pg-smart-search
Production

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, and idColumn are 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_schema to 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 fatal SqlInjectionError rather 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.