Back to Blog
high SEVERITY7 min read

How utils.custom.sql-injection-template-literal happens in JavaScript and how to fix it

A high-severity SQL injection vulnerability was discovered in `CrewRouter-Desktop/src/server-manager.js` at line 266, where a SQL query was constructed using JavaScript template literals with dynamic input. This pattern allows remote attackers to inject arbitrary SQL commands through the web service's request handlers. The fix replaces the unsafe template literal interpolation with parameterized queries, eliminating the injection vector entirely.

O
By Orbis AppSec
Published September 6, 2026Reviewed September 6, 2026

Answer Summary

This is a SQL injection vulnerability (CWE-89) in JavaScript, found in `CrewRouter-Desktop/src/server-manager.js` at line 266, where a SQL query was built using ES6 template literals with unsanitized dynamic input. The fix replaces the template literal string interpolation with parameterized queries (using placeholder `?` values and a separate parameters array), which ensures user-supplied data is never interpreted as SQL syntax.

Vulnerability at a Glance

cweCWE-89
fixReplace template literal interpolation with parameterized queries using placeholder values
riskRemote attackers can execute arbitrary SQL commands against the application database
languageJavaScript (Node.js)
root causeDynamic user input interpolated directly into a SQL query string using JavaScript template literals
vulnerabilitySQL Injection via Template Literal

Introduction

In the CrewRouter-Desktop repository, a high-severity SQL injection vulnerability was identified in src/server-manager.js at line 266. This file manages server lifecycle operations for the CrewRouter Desktop application — an Electron-based wrapper around a web service. Because server-manager.js handles request routing for a web service, the vulnerability sits directly in the path of remote attacker-controlled input, making it immediately exploitable without any special access or chaining.

The core issue: a SQL query was constructed using a JavaScript template literal (\...\${variable}...``) that embedded dynamic input directly into the query string. This classic anti-pattern turns what should be inert data into executable SQL syntax, giving attackers the ability to read, modify, or destroy database contents.

The Vulnerability Explained

What Happened at Line 266

At line 266 of server-manager.js, a SQL query was built using JavaScript's template literal syntax — the backtick strings with ${} interpolation. The pattern looked something like this:

// VULNERABLE: Dynamic input interpolated directly into SQL
const result = db.prepare(`SELECT * FROM servers WHERE id = '${serverId}'`).get();

When serverId comes from a request handler (which it does — this is a web service), an attacker can craft a malicious value that breaks out of the string context and injects arbitrary SQL.

How an Attacker Could Exploit This

Because server-manager.js handles web service requests, an attacker doesn't need local access. They can send a crafted HTTP request with a manipulated parameter. Consider an attacker sending:

serverId = "'; DROP TABLE servers; --"

The resulting SQL becomes:

SELECT * FROM servers WHERE id = ''; DROP TABLE servers; --'

This would:
1. Complete the original query with an empty string match
2. Execute DROP TABLE servers, destroying the entire table
3. Comment out the trailing quote with --

More sophisticated attacks could extract sensitive data using UNION SELECT statements, enumerate database schema, or modify records to escalate privileges within the CrewRouter system.

Why Template Literals Are Dangerous for SQL

JavaScript template literals are syntactic sugar for string concatenation. When you write:

`SELECT * FROM servers WHERE id = '${serverId}'`

The JavaScript engine simply concatenates the string pieces with the value of serverId. There is zero distinction between the SQL structure and the user data — they're all just characters in one string. The database parser has no way to know which parts were intended as SQL commands and which were intended as data values.

This is fundamentally different from parameterized queries, where the SQL structure and the data values travel through separate channels to the database engine.

The Fix

The fix in this pull request replaces the template literal interpolation with parameterized queries. Here's the transformation:

Before (Vulnerable)

// Dynamic input directly embedded in the SQL string
const result = db.prepare(`SELECT * FROM servers WHERE id = '${serverId}'`).get();

After (Secure)

// Parameterized query: SQL structure and data are separated
const result = db.prepare(`SELECT * FROM servers WHERE id = ?`).get(serverId);

Why This Works

With parameterized queries, the database driver processes the fix in two distinct phases:

  1. Parse phase: The SQL string SELECT * FROM servers WHERE id = ? is parsed and compiled. The ? is recognized as a data placeholder, not SQL syntax.
  2. Bind phase: The value of serverId is bound to the placeholder. No matter what characters serverId contains — quotes, semicolons, SQL keywords — they are treated as literal data, never as SQL commands.

Even if an attacker sends '; DROP TABLE servers; --, the database simply searches for a server whose id column literally contains the string '; DROP TABLE servers; --. The attack is neutralized at the architectural level.

Additional Changes

The PR also includes updates to .github/workflows/build-desktop.yml to ensure the server is properly built before packaging the desktop application, and a fix in CrewRouter-Desktop/scripts/stage-server.js that normalizes line endings (.replace(/\r\n/g, '\n')) before performing string matching. This line-ending normalization is a build reliability fix that ensures the staging script works consistently across Windows and Unix environments.

Prevention & Best Practices

1. Always Use Parameterized Queries

Every major Node.js database library supports parameterized queries:

// better-sqlite3
db.prepare('SELECT * FROM users WHERE name = ?').get(userName);

// mysql2
connection.execute('SELECT * FROM users WHERE name = ?', [userName]);

// pg (PostgreSQL)
client.query('SELECT * FROM users WHERE name = $1', [userName]);

2. Lint for Template Literals in SQL Contexts

Use static analysis rules like utils.custom.sql-injection-template-literal in Semgrep to catch template literal usage in SQL query construction during CI/CD. This catches the vulnerability before it reaches production.

3. Apply the Principle of Least Privilege

Configure your database user with minimal permissions. If the application only needs to SELECT from a table, don't grant DROP, DELETE, or ALTER permissions. This limits the blast radius if an injection is ever exploited.

4. Use an ORM or Query Builder

Libraries like Knex.js, Sequelize, or Prisma generate parameterized queries automatically, making it harder to accidentally introduce injection vulnerabilities:

// Knex.js - automatically parameterized
const result = await knex('servers').where('id', serverId).first();

5. Code Review Checklist

Add a specific code review checkpoint: "Does any SQL query use string concatenation or template literals with dynamic values?" If the answer is yes, the code must be refactored before merge.

Key Takeaways

  • **Template literals (\...\${}`) in SQL queries are just as dangerous as string concatenation** — theserver-manager.js` vulnerability at line 266 proves that modern JavaScript syntax doesn't protect against classic injection attacks.
  • Web service request handlers are the highest-risk location for SQL injection — because server-manager.js processes remote requests, this vulnerability was directly exploitable without authentication or chaining.
  • The fix is a one-line structural change — replacing '${serverId}' with ? and passing serverId as a parameter fundamentally changes how the database processes the input, making injection impossible.
  • Automated static analysis catches what code review misses — Semgrep's utils.custom.sql-injection-template-literal rule flagged this exact pattern, demonstrating the value of automated scanning in CI pipelines.
  • Build pipeline changes matter for security — the accompanying workflow fix ensures the server is properly compiled before desktop packaging, preventing stale or unpatched code from shipping.

How Orbis AppSec Detected This

  • Source: Dynamic input from a web service request handler in CrewRouter-Desktop/src/server-manager.js, where user-controlled data flows into the server management logic.
  • Sink: A db.prepare() call at line 266 of server-manager.js where the SQL query string was constructed using a JavaScript template literal with ${} interpolation of the dynamic input.
  • Missing control: No parameterization or sanitization was applied to the dynamic input before it was embedded in the SQL query string. The template literal treated the user input as part of the SQL command structure.
  • CWE: CWE-89 — Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
  • Fix: Replaced the template literal interpolation ('${variable}') with a parameterized query placeholder (?) and passed the dynamic value as a separate argument to the database driver's bind mechanism.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

SQL injection remains one of the most dangerous and prevalent web application vulnerabilities, consistently ranking in the OWASP Top 10. The vulnerability in server-manager.js is a textbook example of how JavaScript's template literal syntax can lull developers into a false sense of security — the code looks clean and modern, but it carries the same injection risk as old-school string concatenation.

The fix is straightforward: use parameterized queries. It's a one-line change that provides a structural guarantee against injection, not just a best-effort filter. If you're building any application that constructs SQL queries with dynamic input, audit your codebase today for template literal interpolation in SQL contexts — and let automated tools like Semgrep and Orbis AppSec catch what manual review might miss.

References

Frequently Asked Questions

What is SQL injection via template literals?

SQL injection via template literals occurs when JavaScript ES6 template strings (backtick syntax with `${}` expressions) are used to embed dynamic, user-controlled values directly into SQL query strings, allowing attackers to manipulate the query's structure and execute arbitrary SQL commands.

How do you prevent SQL injection in JavaScript?

Use parameterized queries (also called prepared statements) where dynamic values are passed as separate parameters using `?` placeholders, rather than interpolating them into the SQL string. Most Node.js database libraries like `better-sqlite3`, `mysql2`, and `pg` support this natively.

What CWE is SQL injection?

SQL injection is classified as CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

Is input validation enough to prevent SQL injection?

No. While input validation adds defense-in-depth, it is not sufficient on its own because it's easy to miss edge cases or encoding tricks. Parameterized queries are the definitive fix because they structurally separate code from data at the database driver level.

Can static analysis detect SQL injection via template literals?

Yes. Static analysis tools like Semgrep can detect patterns where template literals with dynamic expressions are used in SQL query construction. The rule `utils.custom.sql-injection-template-literal` specifically targets this pattern in JavaScript codebases.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

high

How SQL Injection via Template Literals happens in TypeScript and how to fix it

A high-severity SQL injection vulnerability was discovered in the admin panel's database tools where schema names were directly interpolated into SQL queries using JavaScript template literals. The fix replaced unsafe string concatenation with a proper `quoteSchemaLiteral()` function to sanitize inputs before query construction, eliminating the injection vector in two critical database inspection functions.

high

How SQL Injection via Template Literals happens in Node.js and how to fix it

A high-severity SQL injection vulnerability was discovered in server-agents/common/src/search/schema.ts where the `insertRowsBatch` function constructed SQL queries using JavaScript template literals with dynamic input. The fix replaced the vulnerable `db.exec()` call with parameterized queries using `db.query().run()`, eliminating the injection risk in the full-text search merge operation.

high

How SQL Injection happens in Node.js migration scripts and how to fix it

A high-severity SQL injection vulnerability was discovered in `scripts/setup-d1.mjs`, where migration filenames were directly concatenated into SQL INSERT statements using an inadequate `escapeSqlString` function. An attacker with filesystem write access could craft a malicious filename to execute arbitrary SQL commands against the Cloudflare D1 database. The fix replaces string concatenation with parameterized queries, eliminating the injection surface entirely.

critical

How SQL Injection happens in Python database scripts and how to fix it

A critical SQL injection vulnerability was discovered in `MangosSuperUI/Scripts/discover_relationships.py`, where database, table, and column names were interpolated directly into SQL queries using Python f-strings. An attacker controlling these input parameters could execute arbitrary SQL against the database. The fix applies backtick escaping for identifier names and parameterized queries for the `LIMIT` clause.

critical

How SQL Injection happens in Node.js SQLite CLI calls and how to fix it

A critical SQL injection vulnerability was discovered in `lib/ParamediciOSPermissions.js`, where the `service` and `app` variables were interpolated directly into raw SQL strings passed to the `sqlite3` command-line tool without any escaping or parameterization. An attacker with control over these inputs could manipulate the iOS simulator's TCC permission database, potentially granting unauthorized app permissions. The fix applies SQLite-standard single-quote escaping to both variables before th

high

How Unauthenticated Endpoint Exposure Happens in Node.js and How to Fix It

A high-severity unauthenticated endpoint exposure was discovered in `dep/src/server/index.js`, where the `/--ziko--` route served internal application state (`globalThis.Ziko`) to any network-connected client without any authentication or environment guard. The fix adds a single production environment check that returns a `404` before the sensitive data is ever sent. This kind of "debug route left in production" vulnerability is surprisingly common in Node.js applications and can silently leak c