Relay365Multi-model API relay desk
← All desk bulletins
Field notes from the deskEN174 desk reads

Generating SQL Safely With a Language Model

Start With a Narrow SQL Task

Step 1. Build your first version around one reporting question, such as finding monthly order totals. Treat the model as a query author, not an administrator. Your application should validate its output and decide whether execution is allowed. Start with read-only reporting and keep database credentials outside the model’s context. OWASP recommends least-privilege database accounts rather than administrative access. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/Database_Security_Cheat_Sheet.html))

Write down the expected result before prompting. Specify the date range, timezone, currency, and meaning of an order. If those details are missing, have the application request clarification rather than invent business rules.

Provide Only the Schema the Query Needs

Step 2. Prepare a small schema description containing approved tables, columns, data types, and join relationships. For the order report, include order timestamps and amounts, but omit unrelated customer details. Prefer synthetic examples over production records. Treat comments, retrieved documentation, and user questions as untrusted content, not instructions that can override application policy. Prompt injection can arrive through external content as well as direct user input. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html))

Request a Structured SQL Draft

Step 3. Specify the database dialect and request separate fields for the SQL template, parameter values, assumptions, and clarification needs. For example, ask for one PostgreSQL reporting query using only the supplied schema. Require placeholders for dates and other user-supplied values.

Make rejection an acceptable outcome. When the requested column is unavailable or the question is ambiguous, require clarification instead of a guessed query. Treat this output format as an application contract, not a security boundary; OWASP recommends validating model outputs and tool actions independently. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html))

Bind Values Without Trusting Generated Structure

Step 4. Pass values through your database driver’s parameter-binding interface. A PostgreSQL template might contain WHERE created_at >= $1 AND created_at < $2, with timestamps supplied separately. Never concatenate the user’s dates, names, or search terms into SQL. Parameterized queries separate bound data from executable query structure. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html?utm_source=openai))

Parameters do not normally substitute table names, column names, or sort directions. Map those choices to application-owned allowlists. Binding values also does not make an arbitrary model-generated template trustworthy; validate the structure separately. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html?utm_source=openai))

Parse the Query Before Allowing Execution

Step 5. Use a dialect-aware SQL parser to inspect the statement’s structure. SQLGlot, for example, exposes syntax trees that applications can traverse. Build an explicit policy that accepts exactly one approved query, resolves table and column references, and rejects unsupported constructs. A parser supplies structure; your application must supply the authorization rules. ([github.com](https://github.com/tobymao/sqlglot))

Check nested queries and common table expressions, not just the opening keyword. Apply an allowlist to callable functions and reject anything the validator cannot confidently classify. Keep database permissions as a separate enforcement layer rather than relying on parsing alone. ([github.com](https://github.com/tobymao/sqlglot))

Enforce Access and Query Budgets Independently

Step 6. Execute through a dedicated role restricted to approved reporting objects. Enforce tenant isolation independently of model-written filters. PostgreSQL row-level security can restrict visible rows, but table owners typically bypass those policies, so test the actual execution role. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/Database_Security_Cheat_Sheet.html))

Configure statement and lock timeouts for the execution session, and cap returned rows in your application. A result limit is not a substitute for a runtime budget: PostgreSQL provides statement_timeout specifically to abort statements that run too long. ([postgresql.org](https://www.postgresql.org/docs/current/runtime-config-client.html))

Test SQL Meaning Before Expanding Access

Step 7. Run accepted queries against synthetic fixtures with known answers. Include empty results, null amounts, duplicate joins, timezone boundaries, and attempts to access another tenant. Compare results with hand-checked expectations. Test the controls too: unauthorized tables and unsupported statements should be rejected before execution. Keep these checks outside the model, consistent with OWASP’s layered validation guidance. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html))

Compare Model Access Without Moving the Safety Boundary

Step 8. If you want to compare model families, Relay365 is a practical pay-as-you-go relay option with OpenAI SDK compatibility. Keep the same fixtures, validator, and database restrictions when changing models. Create a Relay365 account when you are ready to try that workflow, and review its data-handling terms before sending schema information.

Keep generation replaceable and enforcement application-owned. A useful query should pass both your business-result tests and your access policy before it reaches real data.

Further desk bulletins

Curious how it runs on your own code? Sign up →