If you have been shipping with Windsurf Cascade, the question is fair: when Cascade writes a database query for you, is it writing the parameterized version or the string-concatenated one that ends in a CVE? The answer depends less on Cascade than on what is already in your project, what is in your Cascade rules file, and whether the codebase contains examples of the secure pattern for the model to imitate.
Short answer
Cascade does not guarantee parameterized queries. Industry analysis of LLM coding output during 2025 has flagged that a significant share of generated samples shipped with at least one security flaw, and SQL injection through string concatenation sat among the top categories (Cloud Security Alliance, July 2025). When the prompt asks for a quick query and the file already contains a string-built example, Cascade mirrors that pattern. The fix is to refactor every SQL touchpoint to use parameters, then pin the rule inside your Cascade rules file so the agent stops re-introducing the bug.
What you should know
- Cascade reads your code as much as your prompt. When the rest of the file uses raw template strings to build SQL, the agent will keep producing more of the same.
- Parameterized queries are the OWASP-recommended primary defense. The OWASP SQL Injection Prevention Cheat Sheet lists prepared statements first, ahead of allow-list validation and any manual escaping logic.
- ORMs and query builders close most of the channel. Supabase, Prisma, Drizzle, Knex, and SQLAlchemy abstract the query construction so user input never reaches the SQL string directly.
- Cascade rules files are sticky. A short rule like "always bind SQL parameters, never concatenate user input" stays loaded across sessions and constrains later generations.
- Row Level Security is not a substitute. Postgres RLS protects rows by identity. It does not stop a query whose SQL has been tampered with from running.
How does Cascade end up writing string-concatenated SQL?
Cascade is the agentic assistant inside the Windsurf editor, capable of running up to twenty tool calls per prompt including terminal commands and MCP server actions (Windsurf Cascade documentation). When you ask it to add a user lookup endpoint, it pattern-matches against everything in its training distribution and everything currently visible in the workspace. The shortest path to a working query is often something like:
const sql = `SELECT * FROM users WHERE email = '${email}'`;
const result = await db.query(sql);
That code runs. It passes a manual test with a normal email. It also fails the moment the input contains a single quote, and it fails catastrophically when the input contains a crafted payload like ' OR '1'='1 (OWASP SQL Injection). The Cloud Security Alliance noted in its 2025 briefing that LLMs are rewarded for solving the immediate task, not for choosing the secure idiom, so they default to whichever shape gets a test to pass with the fewest tokens.
The pattern is reinforced by training data. Years of legacy tutorials on the open web show the unsafe shape because the writer wanted to demonstrate raw SQL, not because it was production code. Cascade does not weigh tutorial code differently from production code unless your repository tells it to.
What does a safe refactor actually look like?
The mechanical fix is to move from string interpolation to parameter binding. OWASP lists prepared statements as the first-line defense, followed by allow-list input validation (OWASP SQL Injection Prevention Cheat Sheet). The exact shape depends on the driver.
Postgres via pg in Node:
const result = await db.query(
"SELECT * FROM users WHERE email = $1",
[email]
);
Postgres via Python psycopg:
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
Supabase JavaScript client, which is the most common pairing for AI-coded apps:
const { data, error } = await supabase
.from("users")
.select("*")
.eq("email", email);
The Supabase client never builds raw SQL on the JavaScript side. It converts the chained methods into a PostgREST call, and the server applies your Row Level Security policies (Supabase Row Level Security documentation). Combined, parameterization and RLS form two independent layers. Even if the SQL were somehow injected, RLS still gates the row set by auth.uid().
Take the refactor in three passes. First, grep the project for the unsafe shapes: template literals containing SELECT, INSERT, UPDATE, or DELETE, plus string concatenation around those keywords. Second, replace each occurrence with the driver's parameter binding. Keep the change small, one SQL site per commit, and run the test suite between every replacement so a typo in placeholder syntax surfaces immediately. Third, write a Cascade rules entry. The rule should name the prohibition and the fallback behavior in three lines. Longer rules drift.
When are parameterized queries not enough?
Parameter binding closes the classic injection channel where user input becomes SQL syntax. It does not cover three adjacent failures.
Dynamic identifier injection. If your code lets the user pick a table name or column name, parameter binding will not protect you because identifiers cannot be parameterized. The fix is an allow-list of valid identifiers checked before the query runs.
Stored procedure misuse. The OWASP guidance is explicit that stored procedures themselves do not prevent SQL injection if they internally build SQL from concatenated input. The prepared statement, not the procedure, is what closes the gap.
ORM raw escape hatches. Most ORMs expose a raw query function for cases the query builder cannot model. If Cascade reaches for the raw hatch and concatenates user input, you are back at square one. Watch for db.raw, query.raw, prisma.$queryRaw used as a template literal, and supabase.rpc calls that wrap a string-built statement on the server side.
How does this compare to the broader AI-coded app risk surface?
SQL injection is one bug in a larger family. The pattern of Cascade reusing whatever shape it saw last is the same one that produces hardcoded API keys, missing auth checks, exposed Supabase service role keys, and over-permissive CORS headers. The shape varies, the cause is identical: the agent optimizes for working code, not for the secure idiom.
| Risk | What it looks like in Cascade output | Primary defense |
|---|---|---|
| SQL injection | Template literal building a SELECT or INSERT with ${var} | Parameter binding via driver placeholders |
| Hardcoded secrets | API keys or service role tokens written directly into source | Environment variables, secret scanning in CI |
| Missing auth checks | Endpoint reads from a table with no auth.uid() filter | Postgres RLS plus explicit filter in the client query |
| Over-permissive CORS | Access-Control-Allow-Origin: * on a private API | Per-origin allow-list, strict credentials handling |
| Raw query escape hatch | prisma.$queryRaw with interpolated user input | Use $queryRawUnsafe only with bound parameters, or refactor to typed query |
For builders who want an external automated read of their compiled build before submitting to App Store or Google Play, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS for AI-coded apps, including detection of injection-related patterns in the shipped bundle.
What to watch out for
A few traps catch developers in the middle of an AI-coded SQL cleanup.
Cascade can rewrite the safe version back into the unsafe version. If your rules file is missing or vague, a later prompt asking to "make this faster" can return a string-built query because the model thinks fewer abstractions equal faster code. They do not, and the regression is silent unless tests catch it.
Linters do not flag every concatenated SQL string. ESLint with the security plugin catches some shapes. Semgrep with the right rule set catches more. Neither is exhaustive across multi-line template literals, dynamic identifiers, or ORM escape hatches. A grep-and-review pass on every pull request that touches the database layer is still the most reliable single step.
Pre-submission scanners read the compiled bundle, not your server source. Tools that scan the compiled IPA or APK for OWASP MASVS issues will see hardcoded secrets and missing certificate pinning, but they cannot inspect the server-side SQL shape. SQL injection on the backend is checked in your repository and at the server, not at App Store review. That is a separate gate.
Key takeaways
- Cascade does not guarantee parameterized queries. Treat every SQL statement Cascade writes as a candidate for refactor until you have read it.
- The fix is the OWASP-recommended primary defense: prepared statements and parameter binding applied to every database touchpoint.
- A short Cascade rules entry that names the rule, the prohibition, and the fallback behavior keeps the agent on the safe shape across sessions.
- Layer parameter binding with Postgres Row Level Security so a single mistake does not become a full data breach.
- Some teams outsource pre-submission build scanning to platforms like PTKD.com (https://ptkd.com), particularly when the codebase contains a lot of AI-generated server logic and the team wants a second pair of eyes before shipping.




