CVE-2026-41496 — praisonai
HIGHCVE-2026-41496 is a high-severity (CVSS 8.1) SQL Injection vulnerability in praisonai. A fix is available for praisonai — see the affected versions and patch details below.
PraisonAI: SQL Injection via unvalidated `table_prefix` in 9 conversation store backends (incomplete fix for CVE-2026-40315)
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-41496.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
CVE-2026-41496 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,636 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
praisonai🐍praisonaiagentsReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
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; --"
# ── SQLite (patched) ────────────────────────────────────────────────
try:
if not re.match(r'^[a-zA-Z0-9_]*$', payload):
raise ValueError("blocked")
print(f"[SQLite] FAIL — accepted: {payload}")
except ValueError:
print(f"[SQLite] OK — rejected malicious table_prefix")
# ── MySQL (unpatched) ───────────────────────────────────────────────
sessions_table = f"{payload}sessions"
sql = f"CREATE TABLE IF NOT EXISTS {sessions_table} (session_id VARCHAR(255) PRIMARY KEY)"
print(f"[MySQL] VULN — generated SQL:\n {sql}")
# ── PostgreSQL (unpatched — both table_prefix AND schema) ──────────
schema = "public; DROP SCHEMA data CASCADE; --"
sessions_table = f"{schema}.praison_sessions"
sql = f"CREATE SCHEMA IF NOT EXISTS {schema}"
print(f"[Postgres] VULN — schema injection:\n {sql}")
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} (
session_id VARCHAR(255) PRIMARY KEY, ...
)
""")
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}).
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | praisonai | all versions | 4.5.149pip install --upgrade 'praisonai==4.5.149' |
| 🐍PyPI | praisonaiagents | all versions | 1.6.8pip install --upgrade 'praisonaiagents==1.6.8' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for praisonai, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update praisonai to 4.5.149 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-41496 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-41496 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-41496. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-41496 in your dependencies?
O3 Security finds CVE-2026-41496 across PyPI dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.