FlowiseAI/Flowise · error · Error
Invalid SQL statement: only read-only SELECT/WITH statements
Error message
Invalid SQL statement: only read-only SELECT/WITH statements are allowed
What it means
Thrown by assertReadOnlySqlStatement (packages/components/src/validator.ts:393) when the trimmed statement does not start with SELECT or WITH (case-insensitive, word boundary). The guard ensures only read-only queries run, blocking INSERT/UPDATE/DELETE/PRAGMA/ATTACH/VACUUM and similar — defenses against a compromised LLM writing files via ATTACH DATABASE or VACUUM INTO.
Source
Thrown at packages/components/src/validator.ts:393
*
* @param {string} sql The SQL statement to validate
* @throws {Error} If the statement is not a single read-only SELECT/WITH statement
*/
export const assertReadOnlySqlStatement = (sql: string): void => {
if (!sql || typeof sql !== 'string') {
throw new Error('Invalid SQL statement: statement is required and must be a string')
}
let trimmed = sql.trim()
// Strip at most one trailing semicolon (+ trailing whitespace)
trimmed = trimmed.replace(/;\s*$/, '')
if (trimmed.includes(';')) {
throw new Error('Invalid SQL statement: multiple statements are not allowed')
}
if (!/^(SELECT|WITH)\b/i.test(trimmed)) {
throw new Error('Invalid SQL statement: only read-only SELECT/WITH statements are allowed')
}
if (/load_extension\s*\(/i.test(trimmed)) {
throw new Error('Invalid SQL statement: load_extension is not allowed')
}
}
/**
* Sanitize a file name to prevent path traversal attacks.
* Strips common storage prefixes, extracts the basename, runs it through
* the `sanitize-filename` package, and rejects anything that still looks unsafe.
*
* @param {string} name The file name to sanitize
*/
export const sanitizeFileName = (name: string): string => {
if (!name || typeof name !== 'string') {
throw new Error('Invalid file name: name is required')
}View on GitHub (pinned to abe4a8601a)
Solutions
- Restrict the LLM via system/prompt instructions to SELECT-only against this chain.
- Ensure the database user has read-only grants as defense-in-depth.
- Strip leading line comments before validation if you intentionally allow comments.
- If writes are genuinely needed, use a different node/endpoint — this chain is read-only by design.
Example fix
// before sql = 'INSERT INTO logs VALUES (1)' // LLM tried a write // after sql = 'SELECT * FROM logs WHERE id = 1' // read-only
Defensive patterns
Strategy: validation
Validate before calling
const t = String(sql ?? '').trim();
if (!/^(SELECT|WITH)\b/i.test(t)) throw new Error('only SELECT/WITH allowed');
assertReadOnlySqlStatement(t); Type guard
const isReadOnlyStart = (s: unknown): s is string => typeof s === 'string' && /^(SELECT|WITH)\b/i.test(s.trim());
Try / catch
try { assertReadOnlySqlStatement(sql) } catch (e) { if (e instanceof Error && /only read-only/.test(e.message)) { throw new Error('chain is read-only; use a write-capable node') } else throw e } Prevention
- Prompt the LLM to emit SELECT-only against this chain.
- Grant read-only DB permissions as defense-in-depth.
- Strip leading comments before validation if you allow comments.
When it happens
Trigger: The LLM returns an INSERT/UPDATE/DELETE/CREATE/PRAGMA/ATTACH/VACUUM/etc. statement, or a statement prefixed by a comment/whitespace so it doesn't start with SELECT/WITH.
Common situations: LLM tries to 'fix' the schema with CREATE/ALTER; prompt encourages write operations; comment-prefixed statements; WITH clauses wrapped in leading parens.
Related errors
- Invalid SQL statement: multiple statements are not allowed
- Invalid SQL statement: statement is required and must be a s
- Invalid SQL statement: load_extension is not allowed
- Invalid SQLite path: path traversal attempt detected
- Invalid SQLite path: encoded path traversal attempt detected
AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12).
Data as JSON: /api/errors/3c36cfdb883b433f.
Report an issue: GitHub.