SQL injection In praisonaiagents
Description
PraisonAI: SQL Injection via unvalidated table_prefix in 9 conversation store backends (incomplete fix for CVE-2026-40315)
The fix for CVE-2026-40315 added input validation to SQLiteConversationStore only. Nine sibling backends — MySQL, PostgreSQL, async SQLite/MySQL/PostgreSQL, Turso, SingleStore, Supabase, SurrealDB — pass table_prefix straight into f-string SQL. Same root cause, same code pattern, same exploitation. 52 unvalidated injection points across the codebase.
postgres.py additionally accepts an unvalidated schema parameter used directly in DDL.
Severity
High — CWE-89 (SQL Injection)
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N — 8.1
Exploitable in any deployment where table_prefix is derived from external input (multi-tenant setups, API-driven configuration, user-modifiable config files). Default config ("praison_") is not affected.
Details
The CVE-2026-40315 fix added this guard to sqlite.py:52:
# sqlite.py — PATCHED import re if not re.match(r'^[a-zA-Z0-9_]*$', table_prefix): raise ValueError("table_prefix must contain only alphanumeric characters and underscores")
The following backends perform the identical table_prefix → f-string SQL pattern without this guard:
Backend | File | Line | Injection points |
|---|---|---|---|
MySQL | persistence/conversation/mysql.py | 65 | 5 |
PostgreSQL | persistence/conversation/postgres.py | 89 (+schema:88) | 10 |
Async SQLite | persistence/conversation/async_sqlite.py | 43 | 13 |
Async MySQL | persistence/conversation/async_mysql.py | 65 | 13 |
Async PostgreSQL | persistence/conversation/async_postgres.py | 63 | 13 |
Turso/LibSQL | persistence/conversation/turso.py | 66 | 9 |
SingleStore | persistence/conversation/singlestore.py | 51 | 7 |
Supabase | persistence/conversation/supabase.py | 68 | 9 |
SurrealDB | persistence/conversation/surrealdb.py | 57 | 8 |
Total | 9 backends | 52 injection points |
Additionally, praisonai-agents/praisonaiagents/storage/backends.py:179 (SQLiteBackend) accepts table_name without validation.
PoC
#!/usr/bin/env python3 """ Demonstrates: sqlite.py rejects malicious table_prefix, mysql.py accepts it. Run: python3 poc.py (no dependencies required) """ import re payload = "x'; DROP TABLE users; --"...
Output:
[SQLite] OK — rejected malicious table_prefix [MySQL] VULN — generated SQL: CREATE TABLE IF NOT EXISTS x'; DROP TABLE users; --sessions (session_id VARCHAR(255) PRIMARY KEY) [Postgres] VULN — schema injection: CREATE SCHEMA IF NOT EXISTS public; DROP SCHEMA data CASCADE; --
Vulnerable code (mysql.py, representative)
# mysql.py:65-67 — NO validation self.table_prefix = table_prefix # ← raw input self.sessions_table = f"{table_prefix}sessions" # ← into identifier self.messages_table = f"{table_prefix}messages" # mysql.py:105 — straight into DDL cur.execute(f""" CREATE TABLE IF NOT EXISTS {self.sessions_table} (...
Compare with the patched sqlite.py:52:
# sqlite.py:52-53 — HAS validation if not re.match(r'^[a-zA-Z0-9_]*$', table_prefix): raise ValueError("table_prefix must contain only alphanumeric characters and underscores")
Impact
When table_prefix originates from untrusted input — multi-tenant tenant names, API request parameters, user-editable config — an attacker achieves arbitrary SQL execution against the backing database. The injected SQL runs in the context of DDL and DML operations (CREATE TABLE, INSERT, SELECT, DELETE), giving the attacker read/write/delete access to the entire database.
PostgreSQL's schema parameter adds a second injection vector in DDL (CREATE SCHEMA IF NOT EXISTS {schema}).
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Component | Affected version | Patched versions |
|---|---|---|---|
pypi | 1.6.8 | ||
pypi | 4.5.149 |
Aliases
References