SQL injection via string-interpolated SQL passed to sqflite raw* / execute

ID

dart.code.sql_injection

Severity

critical

Remediation Complexity

hard

Remediation Risk

high

Remediation Effort

medium

Resource

Code

Language

Dart

Description

A sqflite raw query or execute call is built by concatenating attacker-controllable values into the SQL plane ($-interpolation or + concatenation). An attacker can inject arbitrary SQL to read, modify, or destroy database contents.

Rationale

sqflite exposes rawQuery, rawInsert, rawUpdate, rawDelete, and execute, all of which accept a raw SQL string. When that string is produced by Dart string interpolation ("SELECT * FROM users WHERE id = $id") or + concatenation, every character of the interpolated value crosses the language boundary into the SQL parser. The SQL engine cannot tell metadata from data — a value containing ' OR 1=1 --, '; DROP TABLE …, or a UNION SELECT clause changes the statement semantically.

On a mobile device the database is local, so the immediate impact is confidentiality and integrity of the user’s data: an attacker who reaches this call (via deeplink parameter, file import, IPC, sync payload, or a tampered WebView) can read sensitive rows, alter balances or flags, escalate privileges by flipping is_admin-style booleans, or destroy the database with a DROP/DELETE clause. Combined with sqflite_sqlcipher decryption, SQL injection also leaks data the user expected to be encrypted at rest.

The following code illustrates a vulnerable pattern detected by this rule:

class SqlInjectionSamples {
  Future<void> rawQueryInterp(Database db, String userId) async {
    // VULNERABLE: SQL injection via string-interpolated SQL passed to sqflite raw* / execute
    final rows = await db.rawQuery('SELECT * FROM users WHERE id = $userId');
    print('$rows');
  }
}

Remediation

Use parameterized queries: keep the SQL string a literal with ? placeholders, and pass the runtime values in the arguments (a.k.a. whereArgs) named argument. sqflite escapes each value safely. For INSERT/UPDATE/DELETE, prefer the higher-level insert, update, delete helpers which take a column-to-value map.

// Vulnerable: tainted value concatenated into the SQL plane
final rows = await db.rawQuery(
  "SELECT * FROM users WHERE id = $userId");

// Fixed: SQL is a literal, value goes through whereArgs
final rows = await db.rawQuery(
  'SELECT * FROM users WHERE id = ?',
  [userId],
);

// Fixed: higher-level helper escapes value automatically
await db.update(
  'users',
  {'name': name},
  where: 'id = ?',
  whereArgs: [id],
);

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved