GitHub Security Advisory: GHSA-rg3h-x3jw-7jm5
Package: praisonai (PIP)
Severity: HIGH (CVSS 8.1)
CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerable versions: <= 4.5.148
Patched in: 4.5.149

Summary: PraisonAI: SQL Injection via unvalidated `table_prefix` in 9 conversation store backends (incomplete fix for CVE-2026-40315)

Details:
The fix for [CVE-2026-40315](https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x783-xp3g-mqhp) 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](https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x783-xp3g-mqhp) added this guard to `sqlite.py:52`:

```python
# 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

```python
#!/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