Skip to content

Security: devscast/datazen-ts

Security

docs/security.md

Security

Database access can expose critical security risks if user input is handled incorrectly. The most important risk is SQL injection.

SQL injection happens when untrusted input is concatenated into SQL text and changes query semantics.

Datazen helps by supporting prepared statements, but it cannot protect you if you build SQL unsafely.

SQL Injection: Safe and Unsafe APIs for User Input

Assume all SQL-fragment APIs are unsafe unless explicitly listed as safe.

Safe

The following are safe for user-provided scalar values when used as designed:

  • Connection#executeQuery(sql, params, types) values in params
  • Connection#executeStatement(sql, params, types) values in params
  • Connection#insert(table, data, types) values in data
  • Connection#update(table, data, criteria, types) values in data and criteria
  • Connection#delete(table, criteria, types) values in criteria
  • Statement#bindValue() values
  • QueryBuilder#setParameter() values
  • QueryBuilder#createNamedParameter() value
  • QueryBuilder#createPositionalParameter() value
  • QueryBuilder#setFirstResult(offset) numeric offset
  • QueryBuilder#setMaxResults(limit) numeric limit
  • AbstractPlatform#modifyLimitQuery(sql, limit, offset) numeric limit/offset

Unsafe

Treat all of the following as unsafe for direct user input:

  • Raw SQL strings and SQL fragments passed to Connection query methods
  • Most QueryBuilder methods that accept SQL fragments (select, from, where, orderBy, groupBy, join, having, set, values, etc.)
  • Platform SQL expression helpers when you interpolate user input into generated SQL
  • Table names, column names, aliases, and sort expressions unless explicitly validated/allow-listed by application code

User Input in Queries

Wrong: String Concatenation

Never concatenate untrusted input into SQL.

// Wrong
const sql = "SELECT * FROM users WHERE username = '" + username + "'";
await conn.executeQuery(sql);

Right: Prepared Statements

Use placeholders and bind values.

Connection helper:

const result = await conn.executeQuery(
  "SELECT * FROM users WHERE username = ?",
  [username],
);

Named parameters:

const result = await conn.executeQuery(
  "SELECT * FROM users WHERE username = :user",
  { user: username },
);

Statement API:

const stmt = await conn.prepare("SELECT * FROM users WHERE username = :user");
stmt.bindValue("user", username);
const result = await stmt.executeQuery();

QueryBuilder:

const qb = conn
  .createQueryBuilder()
  .select("id", "username")
  .from("users")
  .where("username = :user")
  .setParameter("user", username);

const rows = await qb.fetchAllAssociative();

Parameter Types

Pass explicit types when needed:

  • ParameterType.* for low-level binding semantics
  • Datazen type names ("datetime", "json", etc.) for conversion layer
  • ArrayParameterType.* for list expansion (IN (...) style scenarios)

Using explicit types improves correctness and reduces driver-specific coercion surprises.

Discouraged: Manual Quoting/Escaping

Connection#quote() exists, but prepared statements are strongly preferred. Manual quoting is easier to misuse and harder to review.

// Technically valid, but discouraged
const sql = "SELECT * FROM users WHERE username = " + (await conn.quote(username));
const result = await conn.executeQuery(sql);

For SQL literals generated by platform utilities, use AbstractPlatform#quoteStringLiteral().

Security Checklist

  • Never concatenate untrusted input into SQL text.
  • Bind every user value as a parameter.
  • Do not pass user input as identifiers (table/column/order expressions) without strict allow-listing.
  • Prefer explicit parameter types for non-trivial values.
  • Keep setFirstResult/setMaxResults inputs numeric.

Note

In insert/update/delete, only values are parameterized. Table names and column keys are SQL fragments and must not come from untrusted input without strict allow-listing.

There aren’t any published security advisories