AI-coded apps

    Does Windsurf Cascade write SQL injection into my code?

    Windsurf Cascade AI editor session with SQL query refactor showing parameterized placeholders

    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.

    RiskWhat it looks like in Cascade outputPrimary defense
    SQL injectionTemplate literal building a SELECT or INSERT with ${var}Parameter binding via driver placeholders
    Hardcoded secretsAPI keys or service role tokens written directly into sourceEnvironment variables, secret scanning in CI
    Missing auth checksEndpoint reads from a table with no auth.uid() filterPostgres RLS plus explicit filter in the client query
    Over-permissive CORSAccess-Control-Allow-Origin: * on a private APIPer-origin allow-list, strict credentials handling
    Raw query escape hatchprisma.$queryRaw with interpolated user inputUse $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.
    • #windsurf
    • #cascade
    • #sql injection
    • #ai-coded apps
    • #supabase

    Frequently asked questions

    Will Cascade follow a 'no SQL injection' rule in my Windsurf rules file?
    Cascade reads project rules files at the start of each session and applies them across the conversation. A short, clearly worded rule like 'always bind SQL parameters, never concatenate user input' tends to stick. Longer rules drift, and rules that contradict examples already in the codebase lose to the examples. Keep the rule short and refactor the existing code so Cascade sees the safe pattern in context.
    Should I use an ORM to avoid the problem entirely?
    An ORM closes most of the channel because the query builder itself constructs bound parameters. Prisma, Drizzle, Knex, SQLAlchemy, and the Supabase JavaScript client all build SQL on the server side from typed inputs. The catch is the raw escape hatch. If Cascade reaches for $queryRaw or db.raw and concatenates a string, the ORM no longer protects you. Audit any raw query call by hand.
    Does Supabase Row Level Security protect against SQL injection?
    Row Level Security gates which rows a request can read or write based on the authenticated user identity. It does not stop a query whose SQL has been tampered with from running. Treat RLS as a second layer that limits the blast radius if injection slips through, not a replacement for parameter binding. The two defenses work together, and neither alone is sufficient for sensitive data.
    Is Apple App Review going to flag SQL injection in my backend?
    No, and assuming otherwise is the most common mistake teams make with AI-coded apps. App Review inspects the compiled iOS bundle: code signing, Privacy Manifest, Info.plist permission strings, and a few static patterns. It does not scan your server-side SQL. Injection bugs survive App Store review intact. The check has to happen in your repository or in a build scanner before submission, not at Apple's gate.
    Can a static analyzer catch every SQL injection that Cascade writes?
    Semgrep, ESLint with the security plugin, and CodeQL catch many string-built SQL patterns, especially the obvious template literal shape. None of them is exhaustive across multi-line concatenations, dynamic identifiers, or ORM escape hatches. Treat them as a strong first filter rather than a complete safety net. A manual review on every pull request that touches the database layer is the practical second check.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free