Skip to main content
RapidDev - Software Development Agency
cursor-tutorial

How to Ensure Safe SQL Patterns in Cursor Output

Cursor-generated SQL code often uses string interpolation or concatenation to build queries, creating SQL injection vulnerabilities. By creating .cursor/rules/ that mandate parameterized placeholders, providing a typed query wrapper for Cursor to import, and reinforcing safety in every prompt, you ensure all generated database queries use parameter binding regardless of the query complexity or database engine.

What you'll learn

  • How to enforce parameterized queries across all database engines
  • How to create database-specific rules for PostgreSQL, MySQL, and SQLite
  • How to use Cursor to audit existing code for unsafe SQL patterns
  • How to handle dynamic WHERE clauses safely with parameterized builders
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read15-20 minCursor Free+, any SQL database projectMarch 2026RapidDev Engineering Team
TL;DR

Cursor-generated SQL code often uses string interpolation or concatenation to build queries, creating SQL injection vulnerabilities. By creating .cursor/rules/ that mandate parameterized placeholders, providing a typed query wrapper for Cursor to import, and reinforcing safety in every prompt, you ensure all generated database queries use parameter binding regardless of the query complexity or database engine.

Ensuring safe SQL patterns in Cursor output

Parameterized queries are the foundation of SQL injection prevention. While ORM methods are generally safe, Cursor often generates raw queries with string interpolation for complex scenarios like dynamic filters, bulk operations, and reporting queries. This tutorial establishes comprehensive rules and utilities that ensure every SQL query Cursor generates uses parameter binding.

Prerequisites

  • Cursor installed with a project that uses SQL
  • Basic understanding of SQL injection and parameterized queries
  • A database driver installed (pg, mysql2, better-sqlite3, etc.)
  • Familiarity with Cursor project rules and Chat

Step-by-step guide

1

Create database-specific parameterization rules

Different databases use different placeholder syntax. Create rules that match your specific database engine so Cursor generates the correct parameterized syntax.

.cursor/rules/sql-params.mdc
1---
2description: Enforce parameterized queries for PostgreSQL
3globs: "*.ts,*.js,*.sql,*.repository.ts,*.service.ts"
4alwaysApply: true
5---
6
7# SQL Parameterization Rules (PostgreSQL)
8- ALWAYS use $1, $2, $3 numbered placeholders in queries
9- NEVER use template literals with ${} in SQL strings
10- NEVER concatenate user input into SQL with + operator
11- NEVER use string interpolation in WHERE, INSERT VALUES, or UPDATE SET
12- For dynamic IN clauses: generate $1, $2, ... $N based on array length
13- For dynamic WHERE: build conditions array and join with AND
14- For LIKE/ILIKE: put wildcards in the parameter value, not the query
15
16## Safe Pattern:
17```typescript
18await db.query('SELECT * FROM users WHERE email = $1 AND active = $2', [email, true]);
19```
20
21## Unsafe Pattern (NEVER):
22```typescript
23await db.query(`SELECT * FROM users WHERE email = '${email}'`);
24```

Expected result: Cursor generates PostgreSQL queries with $1/$2 numbered placeholders instead of string interpolation.

2

Build a safe dynamic query builder

Dynamic WHERE clauses are the most common source of SQL injection in Cursor output. Create a utility that builds parameterized dynamic queries safely, then reference it in all database prompts.

src/db/query-builder.ts
1interface QueryBuilder {
2 text: string;
3 params: (string | number | boolean | null)[];
4}
5
6export const buildWhereClause = (
7 filters: Record<string, unknown>
8): QueryBuilder => {
9 const conditions: string[] = [];
10 const params: (string | number | boolean | null)[] = [];
11 let paramIndex = 1;
12
13 for (const [key, value] of Object.entries(filters)) {
14 if (value === undefined) continue;
15
16 if (typeof value === 'string' && key.endsWith('Like')) {
17 const column = key.replace('Like', '');
18 conditions.push(`${column} ILIKE $${paramIndex++}`);
19 params.push(`%${value}%`);
20 } else if (Array.isArray(value)) {
21 const placeholders = value.map(() => `$${paramIndex++}`).join(', ');
22 conditions.push(`${key} IN (${placeholders})`);
23 params.push(...value);
24 } else {
25 conditions.push(`${key} = $${paramIndex++}`);
26 params.push(value as string | number | boolean | null);
27 }
28 }
29
30 return {
31 text: conditions.length ? `WHERE ${conditions.join(' AND ')}` : '',
32 params,
33 };
34};

Expected result: A safe query builder that Cursor imports for all dynamic WHERE clause generation.

3

Generate repository code using the safe patterns

Prompt Cursor to create database access code that uses your parameterized query patterns. Reference both the rules and the query builder utility so Cursor uses them consistently.

Cmd+L prompt
1@sql-params.mdc @src/db/query-builder.ts
2
3Create an OrderRepository with these methods:
41. findAll(filters: OrderFilters) dynamic WHERE with buildWhereClause
52. findById(id: string) parameterized $1
63. findByDateRange(start: Date, end: Date) parameterized BETWEEN
74. create(data: CreateOrderDTO) parameterized INSERT with RETURNING
85. bulkUpdateStatus(ids: string[], status: string) parameterized IN clause
9
10All queries MUST use parameterized placeholders.
11Use buildWhereClause for dynamic filtering.
12Never use string interpolation in any SQL.

Expected result: Cursor generates a repository with parameterized queries for every method, using the query builder for dynamic filtering.

4

Audit existing code for unsafe SQL patterns

Use Cursor to scan your entire codebase for SQL injection vulnerabilities. The @codebase context lets Cursor search across all files for dangerous patterns like template literals near SQL keywords.

Cmd+L prompt
1@sql-params.mdc @codebase
2
3Search the entire codebase for SQL injection vulnerabilities:
41. Template literals containing SQL keywords with ${} interpolation
52. String concatenation with + near SQL query strings
63. Raw query calls without parameterized placeholders
74. LIKE clauses with interpolated wildcards
85. IN clauses built with string joining
9
10For each vulnerability:
11- Show the file and line number
12- Explain the injection risk
13- Show the parameterized fix

Expected result: Cursor identifies all SQL injection risks in the codebase with specific parameterized fixes.

5

Test parameterized queries with injection attempts

Generate tests that verify your queries are actually parameterized by attempting SQL injection payloads. These tests confirm that the parameterized approach prevents injection regardless of input.

Cmd+L prompt
1@sql-params.mdc @src/repositories/order.repository.ts
2
3Generate security tests for OrderRepository that verify SQL injection
4prevention. For each method, test with these injection payloads:
51. "'; DROP TABLE orders; --"
62. "1' OR '1'='1"
73. "1; UPDATE users SET role='admin'"
84. "' UNION SELECT * FROM users --"
9
10Each test should verify:
11- The query executes without error
12- No unintended data is returned or modified
13- The injection payload is treated as a literal string value

Expected result: Cursor generates injection test cases that verify parameterized queries treat all input as literal values.

Complete working example

src/db/query-builder.ts
1interface QueryBuilder {
2 text: string;
3 params: (string | number | boolean | null)[];
4}
5
6export const buildWhereClause = (
7 filters: Record<string, unknown>
8): QueryBuilder => {
9 const conditions: string[] = [];
10 const params: (string | number | boolean | null)[] = [];
11 let paramIndex = 1;
12
13 for (const [key, value] of Object.entries(filters)) {
14 if (value === undefined) continue;
15
16 if (typeof value === 'string' && key.endsWith('Like')) {
17 const column = key.replace('Like', '');
18 conditions.push(`${column} ILIKE $${paramIndex++}`);
19 params.push(`%${value}%`);
20 } else if (Array.isArray(value)) {
21 const placeholders = value.map(() => `$${paramIndex++}`).join(', ');
22 conditions.push(`${key} IN (${placeholders})`);
23 params.push(...(value as (string | number | boolean | null)[]));
24 } else if (value === null) {
25 conditions.push(`${key} IS NULL`);
26 } else {
27 conditions.push(`${key} = $${paramIndex++}`);
28 params.push(value as string | number | boolean);
29 }
30 }
31
32 return {
33 text: conditions.length ? `WHERE ${conditions.join(' AND ')}` : '',
34 params,
35 };
36};
37
38export const buildInsert = (
39 table: string,
40 data: Record<string, unknown>
41): QueryBuilder => {
42 const keys = Object.keys(data);
43 const values = Object.values(data) as (string | number | boolean | null)[];
44 const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
45
46 return {
47 text: `INSERT INTO ${table} (${keys.join(', ')}) VALUES (${placeholders}) RETURNING *`,
48 params: values,
49 };
50};

Common mistakes when ensuring Safe SQL Patterns in Cursor Output

Why it's a problem: Parameterizing simple queries but not dynamic WHERE clauses

How to avoid: Create a buildWhereClause utility and reference it in your rules. Cursor uses the utility for dynamic queries instead of string interpolation.

Why it's a problem: Putting wildcards in the SQL string instead of the parameter

How to avoid: Show the correct LIKE pattern in your rules: ILIKE $1 with params.push('%' + search + '%').

Why it's a problem: Not testing with actual injection payloads

How to avoid: Generate and run security tests with common injection payloads to verify parameterization prevents them.

Best practices

  • Create a typed query builder utility for dynamic WHERE clauses
  • Reference package.json and database driver in prompts for correct placeholder syntax
  • Test with SQL injection payloads after every database code change
  • Use @codebase audits periodically to catch unsafe patterns
  • Keep raw SQL queries in dedicated repository files for easier review
  • Use ORMs for simple CRUD and parameterized raw queries for complex operations
  • Never trust that Cursor's output is safe — always review SQL code manually

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Create a safe dynamic query builder for PostgreSQL that generates parameterized WHERE clauses from a filter object. Support equality, ILIKE, IN, NULL, date ranges, and pagination. All user input must go through $1/$2 parameters.

Cursor Prompt

@sql-params.mdc @src/db/query-builder.ts Generate a ReportRepository with complex queries: monthly revenue aggregation, top products by sales, customer retention rates. All queries MUST use parameterized $1/$2 placeholders. Use buildWhereClause for dynamic date range filtering.

Frequently asked questions

Are ORMs like Prisma or TypeORM always safe from injection?

Standard ORM methods are safe, but raw query methods (Prisma.$queryRaw, TypeORM query builder with .where(string)) can be vulnerable. Add ORM-specific rules for raw query usage.

What about NoSQL injection in MongoDB?

MongoDB has its own injection risks through operator injection. Create separate rules for MongoDB that ban $where and mandate sanitized query objects.

How do I parameterize ORDER BY clauses?

Most databases do not support parameterized ORDER BY. Validate the column name against a whitelist of allowed columns, then interpolate it safely (not from user input directly).

Should I use prepared statements or parameterized queries?

Both are safe. Prepared statements offer a small performance benefit for repeated queries. Parameterized queries are simpler for one-off queries. Both prevent SQL injection.

Can Cursor detect SQL injection in existing code?

Yes. Use the @codebase audit prompt to scan for template literals near SQL keywords. Cursor identifies most common injection patterns but may miss subtle edge cases.

Can RapidDev help secure our database layer?

Yes. RapidDev provides database security audits, builds parameterized query utilities, and configures Cursor rules and CI scanning for ongoing SQL injection prevention.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.