Home / Articles

From NL-to-SQL demos to a sandboxed AI analytics assistant

From NL to SQL demos to a sandboxed AI analytics assistant: schema allowlists, static checks, isolated execution, result caps and audit logs on every query.

Publisher: REACTAPP.TOP4 min read1006 words

This article is published in English.

Sources: From “Natural Language to SQL” to a Secure AI Analytics Assistant: An Architecture Evolution

Letting a language model write and run code for analytics is a defense-in-depth problem, not a prompt trick.

The starting problem

Business users have questions—“average house price by city,” “top ten expensive properties,” “highest price per square foot”—but not SQL skills. The obvious fix is NL-to-SQL: ask in English, generate a query, run it.

Version one: straightforward NL-to-SQL

Early versions generate SQL from the question and execute against the warehouse. Demos impress. Then someone asks for a calculation SQL expresses poorly, or the model invents a column, or a crafted prompt tries DROP TABLE.

SELECT city, AVG(house_price) AS avg_price
FROM house_pricing
GROUP BY city;
SELECT department, AVG(salary) FROM employees GROUP BY department;
result = (
df.groupby("department")["salary"]
.mean()
.reset_index()
)
import os
os.remove("file.csv")
import subprocess
subprocess.run(…)

Pivoting away from SQL entirely

A more flexible path: let the model write short Python against a constrained dataframe API instead of raw SQL strings. That unlocks pandas-style analysis—but also expands the blast radius if execution is unchecked.

Making prompting reliable

Reliable prompting means:

  • schema cards with allowed tables/columns only
  • few-shot examples of safe patterns
  • explicit refusal when the question needs disallowed fields
  • structured output (code block fences or JSON) the runner can parse

The problem that mattered most: executing LLM-generated code

Execution is the security boundary. Never run model code in the app process with full privileges. Use a sandbox: no network, limited CPU/memory, allowlisted imports, timeouts, and a read-only view of approved datasets.

Layering in defenses

Defense in depth stacks:

  1. Prompt firewall — reject jailbreaks and out-of-scope asks
  2. Schema allowlist — model only sees approved fields
  3. Static checks — ban imports, file I/O, eval, subprocess
  4. Sandbox runtime — isolate execution
  5. Result caps — row/byte limits before returning to the UI
  6. Audit log — who asked, what code ran, what was returned

Making it resilient, not just safe

Retries on transient failures, deterministic seeds where possible, cached schema cards, and human escalation when the model cannot answer safely. Safety without resilience just produces polite errors.

The architecture that emerged

Gateway → policy → planner/prompt → sandboxed executor → formatter → audit. SQL may still appear as an internal tool for simple filters, but Python-in-a-box handled the messy analytical asks without opening the warehouse to arbitrary statements.

Why this ended up the more interesting project

NL-to-SQL is a translation demo. Secure analytics assistants are product engineering: threat models, sandboxes, allowlists, and UX that explains refusals. That is the work worth keeping.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.

Ship a golden suite: benign questions, schema-probing questions, and malicious import attempts. Fail the build if any malicious case executes outside the sandbox or if a benign case falsely refuses without a clear reason.