CVE-2026-60090
PraisonAI before 4.6.78 SQL/CQL Injection via vector dimension
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.3
PraisonAI before 4.6.78 SQL/CQL Injection via vector dimension
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
- Record
CVE-2026-60090- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
EUVDEUVD-2026-43175
9.3
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
- Record
EUVD-2026-43175- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-q65p-7p84-495c
9.3
Duplicate Advisory: PraisonAI: PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-wf65-4jjx-q444. This link is maintained to preserve external references.
Original Description
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
- Record
GHSA-q65p-7p84-495c- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-wf65-4jjx-q444 No score
PraisonAI: PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
Summary
The PGVector and Cassandra knowledge-store backends validate SQL/CQL identifiers such as schema, keyspace, and collection names, but still insert the caller-controlled dimension argument directly into CREATE TABLE vector column declarations. A caller that can influence collection creation dimensions can append SQL/CQL tokens to the generated DDL executed by the database driver.
Technical Details
The affected boundary is the vector-store collection creation API. The shared KnowledgeStore.create_collection() contract declares dimension: int, but Python type hints are not enforced at runtime. Backends that interpolate that value into DDL must validate the runtime value before constructing SQL/CQL.
src/praisonai/praisonai/persistence/knowledge/pgvector.py already treats DDL identifier interpolation as security-sensitive: __init__() calls validate_identifier(schema, name="schema"), and _table_name() calls validate_identifier(collection, name="collection name") before returning f"{self.schema}.praison_vec_{collection}". However, PGVectorKnowledgeStore.create_collection() then executes:
cur.execute(f"""
CREATE TABLE IF NOT EXISTS {table} (
id VARCHAR(255) PRIMARY KEY,
content TEXT,
content_hash VARCHAR(64),
created_at DOUBLE PRECISION,
metadata JSONB,
embedding vector({dimension})
)
""")
No equivalent type or range check runs on dimension. Passing a string such as 3); DROP TABLE tenant_secrets; -- reaches the SQL sent to cur.execute().
src/praisonai/praisonai/persistence/knowledge/cassandra.py has the same pattern. The constructor validates keyspace, and create_collection() validates the collection name, but the vector column DDL uses:
self._session.execute(f"""
CREATE TABLE IF NOT EXISTS {name} (
id text PRIMARY KEY,
content text,
content_hash text,
created_at double,
embedding vector<float, {dimension}>
)
""")
Passing a string such as 3>; DROP TABLE tenant_secrets; -- reaches the CQL sent to session.execute().
PoV
This minimal PoV imports the real backend classes with fake database drivers, records the statements sent to the drivers, and compares a safe integer dimension with a malicious string dimension. It also attempts a malicious collection name as a negative control; current code rejects that name, proving the identifier hardening is active while the vector dimension remains unguarded.
#!/usr/bin/env python3
"""Local PoV for vector-store dimension DDL interpolation.
The script imports PraisonAI's current source with fake PostgreSQL/Cassandra
drivers, then records the SQL/CQL sent to the driver cursors. No database server
is required; the assertion is that the real classes build executable DDL with an
attacker-controlled dimension string.
"""
from __future__ import annotations
import argparse
import importlib
import json
import subprocess
import sys
import types
from pathlib import Path
from typing import Any
class SqlRecorder:
def __init__(self) -> None:
self.statements: list[dict[str, Any]] = []
def execute(self, statement: str, params: Any = None) -> None:
normalized = "\n".join(line.rstrip() for line in statement.strip().splitlines())
self.statements.append({"statement": normalized, "params": params})
def __enter__(self) -> "SqlRecorder":
return self
def __exit__(self, *_exc: object) -> None:
return None
class FakeConnection:
def __init__(self, recorder: SqlRecorder) -> None:
self.recorder = recorder
def cursor(self, *args: Any, **kwargs: Any) -> SqlRecorder:
return self.recorder
def commit(self) -> None:
return None
class FakePool:
def __init__(self, recorder: SqlRecorder) -> None:
self.conn = FakeConnection(recorder)
…
- Record
GHSA-wf65-4jjx-q444- Weakness
- CWE-89, CWE-943
- Updated
- 2026-11-02
NVD
9.3
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
- Record
CVE-2026-60090- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
OSV
9.3
PraisonAI before 4.6.78 SQL/CQL Injection via vector dimension
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
- Record
CVE-2026-60090- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-89
- Updated
- 2026-11-02
OSVGHSA-q65p-7p84-495c
9.3
Duplicate Advisory: PraisonAI: PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-wf65-4jjx-q444. This link is maintained to preserve external references.
Original Description
PraisonAI before 4.6.78 fails to validate the caller-controlled dimension argument in the PGVector and Cassandra knowledge-store create_collection() backends. Although schema, keyspace, and collection-name identifiers are validated, the dimension value (declared as int but not enforced at runtime) is interpolated directly into the vector column of the generated CREATE TABLE DDL. A caller able to influence collection-creation dimensions can pass a string such as '3); DROP TABLE tenant_secrets; --' to inject SQL/CQL tokens into the statement executed by the database driver.
- Record
GHSA-q65p-7p84-495c- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-89
- Updated
- 2026-11-02
OSVGHSA-wf65-4jjx-q444 No score
PraisonAI: PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
Summary
The PGVector and Cassandra knowledge-store backends validate SQL/CQL identifiers such as schema, keyspace, and collection names, but still insert the caller-controlled dimension argument directly into CREATE TABLE vector column declarations. A caller that can influence collection creation dimensions can append SQL/CQL tokens to the generated DDL executed by the database driver.
Technical Details
The affected boundary is the vector-store collection creation API. The shared KnowledgeStore.create_collection() contract declares dimension: int, but Python type hints are not enforced at runtime. Backends that interpolate that value into DDL must validate the runtime value before constructing SQL/CQL.
src/praisonai/praisonai/persistence/knowledge/pgvector.py already treats DDL identifier interpolation as security-sensitive: __init__() calls validate_identifier(schema, name="schema"), and _table_name() calls validate_identifier(collection, name="collection name") before returning f"{self.schema}.praison_vec_{collection}". However, PGVectorKnowledgeStore.create_collection() then executes:
cur.execute(f"""
CREATE TABLE IF NOT EXISTS {table} (
id VARCHAR(255) PRIMARY KEY,
content TEXT,
content_hash VARCHAR(64),
created_at DOUBLE PRECISION,
metadata JSONB,
embedding vector({dimension})
)
""")
No equivalent type or range check runs on dimension. Passing a string such as 3); DROP TABLE tenant_secrets; -- reaches the SQL sent to cur.execute().
src/praisonai/praisonai/persistence/knowledge/cassandra.py has the same pattern. The constructor validates keyspace, and create_collection() validates the collection name, but the vector column DDL uses:
self._session.execute(f"""
CREATE TABLE IF NOT EXISTS {name} (
id text PRIMARY KEY,
content text,
content_hash text,
created_at double,
embedding vector<float, {dimension}>
)
""")
Passing a string such as 3>; DROP TABLE tenant_secrets; -- reaches the CQL sent to session.execute().
PoV
This minimal PoV imports the real backend classes with fake database drivers, records the statements sent to the drivers, and compares a safe integer dimension with a malicious string dimension. It also attempts a malicious collection name as a negative control; current code rejects that name, proving the identifier hardening is active while the vector dimension remains unguarded.
#!/usr/bin/env python3
"""Local PoV for vector-store dimension DDL interpolation.
The script imports PraisonAI's current source with fake PostgreSQL/Cassandra
drivers, then records the SQL/CQL sent to the driver cursors. No database server
is required; the assertion is that the real classes build executable DDL with an
attacker-controlled dimension string.
"""
from __future__ import annotations
import argparse
import importlib
import json
import subprocess
import sys
import types
from pathlib import Path
from typing import Any
class SqlRecorder:
def __init__(self) -> None:
self.statements: list[dict[str, Any]] = []
def execute(self, statement: str, params: Any = None) -> None:
normalized = "\n".join(line.rstrip() for line in statement.strip().splitlines())
self.statements.append({"statement": normalized, "params": params})
def __enter__(self) -> "SqlRecorder":
return self
def __exit__(self, *_exc: object) -> None:
return None
class FakeConnection:
def __init__(self, recorder: SqlRecorder) -> None:
self.recorder = recorder
def cursor(self, *args: Any, **kwargs: Any) -> SqlRecorder:
return self.recorder
def commit(self) -> None:
return None
class FakePool:
def __init__(self, recorder: SqlRecorder) -> None:
self.conn = FakeConnection(recorder)
…
- Record
GHSA-wf65-4jjx-q444- Weakness
- CWE-89, CWE-943
- Updated
- 2026-11-02
OSVPYSEC-2026-4260 No score
PraisonAI: PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL
Summary
The PGVector and Cassandra knowledge-store backends validate SQL/CQL identifiers such as schema, keyspace, and collection names, but still insert the caller-controlled dimension argument directly into CREATE TABLE vector column declarations. A caller that can influence collection creation dimensions can append SQL/CQL tokens to the generated DDL executed by the database driver.
Technical Details
The affected boundary is the vector-store collection creation API. The shared KnowledgeStore.create_collection() contract declares dimension: int, but Python type hints are not enforced at runtime. Backends that interpolate that value into DDL must validate the runtime value before constructing SQL/CQL.
src/praisonai/praisonai/persistence/knowledge/pgvector.py already treats DDL identifier interpolation as security-sensitive: __init__() calls validate_identifier(schema, name="schema"), and _table_name() calls validate_identifier(collection, name="collection name") before returning f"{self.schema}.praison_vec_{collection}". However, PGVectorKnowledgeStore.create_collection() then executes:
cur.execute(f"""
CREATE TABLE IF NOT EXISTS {table} (
id VARCHAR(255) PRIMARY KEY,
content TEXT,
content_hash VARCHAR(64),
created_at DOUBLE PRECISION,
metadata JSONB,
embedding vector({dimension})
)
""")
No equivalent type or range check runs on dimension. Passing a string such as 3); DROP TABLE tenant_secrets; -- reaches the SQL sent to cur.execute().
src/praisonai/praisonai/persistence/knowledge/cassandra.py has the same pattern. The constructor validates keyspace, and create_collection() validates the collection name, but the vector column DDL uses:
self._session.execute(f"""
CREATE TABLE IF NOT EXISTS {name} (
id text PRIMARY KEY,
content text,
content_hash text,
created_at double,
embedding vector<float, {dimension}>
)
""")
Passing a string such as 3>; DROP TABLE tenant_secrets; -- reaches the CQL sent to session.execute().
PoV
This minimal PoV imports the real backend classes with fake database drivers, records the statements sent to the drivers, and compares a safe integer dimension with a malicious string dimension. It also attempts a malicious collection name as a negative control; current code rejects that name, proving the identifier hardening is active while the vector dimension remains unguarded.
#!/usr/bin/env python3
"""Local PoV for vector-store dimension DDL interpolation.
The script imports PraisonAI's current source with fake PostgreSQL/Cassandra
drivers, then records the SQL/CQL sent to the driver cursors. No database server
is required; the assertion is that the real classes build executable DDL with an
attacker-controlled dimension string.
"""
from __future__ import annotations
import argparse
import importlib
import json
import subprocess
import sys
import types
from pathlib import Path
from typing import Any
class SqlRecorder:
def __init__(self) -> None:
self.statements: list[dict[str, Any]] = []
def execute(self, statement: str, params: Any = None) -> None:
normalized = "\n".join(line.rstrip() for line in statement.strip().splitlines())
self.statements.append({"statement": normalized, "params": params})
def __enter__(self) -> "SqlRecorder":
return self
def __exit__(self, *_exc: object) -> None:
return None
class FakeConnection:
def __init__(self, recorder: SqlRecorder) -> None:
self.recorder = recorder
def cursor(self, *args: Any, **kwargs: Any) -> SqlRecorder:
return self.recorder
def commit(self) -> None:
return None
class FakePool:
def __init__(self, recorder: SqlRecorder) -> None:
self.conn = FakeConnection(recorder)
…
- Record
PYSEC-2026-4260- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
praisonaiPyPI |
through 4.6.77 | 4.6.78 |
praisonaiPyPI |
before 4.6.78, plus listed versions 0.0.10.0.100.0.110.0.120.0.13 Show 45 moreShow less0.0.140.0.150.0.160.0.170.0.180.0.190.0.20.0.200.0.210.0.220.0.230.0.240.0.250.0.260.0.270.0.280.0.290.0.30.0.300.0.310.0.320.0.330.0.340.0.350.0.360.0.370.0.380.0.390.0.40.0.400.0.410.0.420.0.430.0.440.0.450.0.460.0.470.0.480.0.490.0.50.0.500.0.520.0.530.0.540.0.55 |
4.6.78 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-60090 is a critical vulnerability (CVSS 9.3) in Mervinpraison Praisonai, published July 11, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.70%, the 53rd percentile. Fixed in praisonai 4.6.78.
References
github.com/MervinPraison/PraisonAI/security/advisories/GHSA-wf65-4jjx-q444
github.com/MervinPraison/PraisonAI/commit/3aa9cbc2bd49c23a32be0a89a5e620d13d843eab
vulncheck.com/advisories/praisonai-before-sql-cql-injection-via-vector-dimension
nvd.nist.gov/vuln/detail/CVE-2026-60090
github.com/advisories/GHSA-q65p-7p84-495c
github.com/advisories/GHSA-wf65-4jjx-q444
github.com/CVEProject/cvelistV5/tree/main/cves/2026/60xxx/CVE-2026-60090.json
github.com/MervinPraison/PraisonAI
pypi.org/project/praisonai
Left out here: 713 versions and the rest of 3 source descriptions. The JSON record has them.