fix(security): SQL injection in Python/JS clients + bare except + session leak
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
- Fix SQL injection vulnerabilities in chat_history.py, langchain_store.py, rag_pipeline.py, baradb_langchain.js by switching to parameterized queries - Replace dangerous bare except: with except CatchableError:/ValueError: in llm.nim, embed.nim, cypher.nim, mcp/server.nim, executor.nim - Fix session variable leak in MCP handleVectorSearch/handleSchemaInspect - Build: 0 errors, all tests pass
This commit is contained in:
@@ -5,7 +5,7 @@ Implements LangChain's BaseChatMessageHistory interface backed by BaraDB.
|
||||
Supports multi-tenant isolation via tenant_id and user_id.
|
||||
|
||||
Usage:
|
||||
from baradb import Client
|
||||
from baradb import Client, WireValue
|
||||
from baradb.chat_history import BaraDBChatHistory
|
||||
|
||||
client = Client("localhost", 9472)
|
||||
@@ -59,7 +59,7 @@ class BaraDBChatHistory:
|
||||
async def _ensure_table(self):
|
||||
if self._initialized:
|
||||
return
|
||||
await self.client.execute(
|
||||
await self.client.query(
|
||||
f"""
|
||||
CREATE TABLE IF NOT EXISTS {self.table} (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -73,7 +73,7 @@ class BaraDBChatHistory:
|
||||
)
|
||||
"""
|
||||
)
|
||||
await self.client.execute(
|
||||
await self.client.query(
|
||||
f"CREATE INDEX IF NOT EXISTS idx_{self.table}_session "
|
||||
f"ON {self.table}(session_id) USING btree"
|
||||
)
|
||||
@@ -98,14 +98,23 @@ class BaraDBChatHistory:
|
||||
created_at = datetime.utcnow().isoformat()
|
||||
|
||||
for key, val in self._build_session().items():
|
||||
await self.client.execute(f"SET {key} = '{val}'")
|
||||
await self.client.query_params(
|
||||
f"SET {key} = $1", [self._wire_string(val)]
|
||||
)
|
||||
|
||||
await self.client.execute(
|
||||
f"INSERT INTO {self.table} (id, session_id, role, content, metadata, "
|
||||
f"tenant_id, user_id, created_at) "
|
||||
f"VALUES ('{msg_id}', '{self.session_id}', '{role}', "
|
||||
f"'{_escape(content)}', '{_escape(metadata)}', "
|
||||
f"'{self.tenant_id or ''}', '{self.user_id or ''}', '{created_at}')"
|
||||
await self.client.query_params(
|
||||
f"INSERT INTO {self.table} (id, session_id, role, content, metadata, tenant_id, user_id, created_at) "
|
||||
f"VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
[
|
||||
self._wire_string(msg_id),
|
||||
self._wire_string(self.session_id),
|
||||
self._wire_string(role),
|
||||
self._wire_string(content),
|
||||
self._wire_string(metadata),
|
||||
self._wire_string(self.tenant_id or ""),
|
||||
self._wire_string(self.user_id or ""),
|
||||
self._wire_string(created_at),
|
||||
],
|
||||
)
|
||||
|
||||
def add_user_message(self, message: Any) -> None:
|
||||
@@ -132,14 +141,22 @@ class BaraDBChatHistory:
|
||||
created_at = datetime.utcnow().isoformat()
|
||||
|
||||
for key, val in self._build_session().items():
|
||||
await self.client.execute(f"SET {key} = '{val}'")
|
||||
await self.client.query_params(
|
||||
f"SET {key} = $1", [self._wire_string(val)]
|
||||
)
|
||||
|
||||
await self.client.execute(
|
||||
f"INSERT INTO {self.table} (id, session_id, role, content, "
|
||||
f"tenant_id, user_id, created_at) "
|
||||
f"VALUES ('{msg_id}', '{self.session_id}', '{role}', "
|
||||
f"'{_escape(content)}', '{self.tenant_id or ''}', "
|
||||
f"'{self.user_id or ''}', '{created_at}')"
|
||||
await self.client.query_params(
|
||||
f"INSERT INTO {self.table} (id, session_id, role, content, tenant_id, user_id, created_at) "
|
||||
f"VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
[
|
||||
self._wire_string(msg_id),
|
||||
self._wire_string(self.session_id),
|
||||
self._wire_string(role),
|
||||
self._wire_string(content),
|
||||
self._wire_string(self.tenant_id or ""),
|
||||
self._wire_string(self.user_id or ""),
|
||||
self._wire_string(created_at),
|
||||
],
|
||||
)
|
||||
|
||||
async def get_messages(self) -> List[Any]:
|
||||
@@ -154,13 +171,19 @@ class BaraDBChatHistory:
|
||||
return f"{self.type}: {self.content}"
|
||||
|
||||
for key, val in self._build_session().items():
|
||||
await self.client.execute(f"SET {key} = '{val}'")
|
||||
await self.client.query_params(
|
||||
f"SET {key} = $1", [self._wire_string(val)]
|
||||
)
|
||||
|
||||
result = await self.client.execute(
|
||||
result = await self.client.query_params(
|
||||
f"SELECT role, content FROM {self.table} "
|
||||
f"WHERE session_id = '{self.session_id}' "
|
||||
f"WHERE session_id = $1 "
|
||||
f"ORDER BY created_at ASC "
|
||||
f"LIMIT {self.max_messages}"
|
||||
f"LIMIT $2",
|
||||
[
|
||||
self._wire_string(self.session_id),
|
||||
self._wire_int(self.max_messages),
|
||||
],
|
||||
)
|
||||
messages = []
|
||||
if result and hasattr(result, "rows"):
|
||||
@@ -180,9 +203,12 @@ class BaraDBChatHistory:
|
||||
async def clear(self) -> None:
|
||||
await self._ensure_table()
|
||||
for key, val in self._build_session().items():
|
||||
await self.client.execute(f"SET {key} = '{val}'")
|
||||
await self.client.execute(
|
||||
f"DELETE FROM {self.table} WHERE session_id = '{self.session_id}'"
|
||||
await self.client.query_params(
|
||||
f"SET {key} = $1", [self._wire_string(val)]
|
||||
)
|
||||
await self.client.query_params(
|
||||
f"DELETE FROM {self.table} WHERE session_id = $1",
|
||||
[self._wire_string(self.session_id)],
|
||||
)
|
||||
|
||||
async def get_session_summary(self, max_tokens: int = 2000) -> str:
|
||||
@@ -197,6 +223,13 @@ class BaraDBChatHistory:
|
||||
total_chars += len(text)
|
||||
return "\n".join(parts)
|
||||
|
||||
@staticmethod
|
||||
def _wire_string(val: str) -> Any:
|
||||
# Lazy import to avoid circular dependency
|
||||
from baradb import WireValue
|
||||
return WireValue.string(val)
|
||||
|
||||
def _escape(s: str) -> str:
|
||||
return s.replace("'", "''").replace("\\", "\\\\")
|
||||
@staticmethod
|
||||
def _wire_int(val: int) -> Any:
|
||||
from baradb import WireValue
|
||||
return WireValue.int64(val)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
BaraDB LangChain Vector Store Integration
|
||||
|
||||
Usage:
|
||||
from baradb import Client
|
||||
from baradb import Client, WireValue
|
||||
from baradb.langchain_store import BaraDBStore
|
||||
from langchain.embeddings import OpenAIEmbeddings
|
||||
|
||||
@@ -50,6 +50,19 @@ class BaraDBStore:
|
||||
self.vector_dimension = vector_dimension
|
||||
self._table_created = False
|
||||
|
||||
def _wire(self, val: Any) -> Any:
|
||||
"""Lazy import WireValue to avoid circular deps."""
|
||||
from baradb import WireValue
|
||||
if isinstance(val, str):
|
||||
return WireValue.string(val)
|
||||
if isinstance(val, int):
|
||||
return WireValue.int64(val)
|
||||
if isinstance(val, float):
|
||||
return WireValue.float64(val)
|
||||
if val is None:
|
||||
return WireValue.null()
|
||||
return WireValue.string(str(val))
|
||||
|
||||
async def _ensure_table(self) -> None:
|
||||
if self._table_created:
|
||||
return
|
||||
@@ -84,24 +97,23 @@ class BaraDBStore:
|
||||
vec_str = "[" + ",".join(str(v) for v in vec) + "]"
|
||||
|
||||
meta = metadatas[i] if metadatas and i < len(metadatas) else {}
|
||||
meta_cols = []
|
||||
meta_vals = []
|
||||
col_names = [self.embedding_col, self.text_col]
|
||||
params = [self._wire(vec_str), self._wire(text)]
|
||||
|
||||
if self.tenant_id:
|
||||
meta_cols.append("tenant_id")
|
||||
meta_vals.append(f"'{self.tenant_id}'")
|
||||
col_names.append("tenant_id")
|
||||
params.append(self._wire(self.tenant_id))
|
||||
for mc in self.metadata_cols:
|
||||
if mc in meta:
|
||||
meta_cols.append(mc)
|
||||
meta_vals.append(f"'{meta[mc]}'")
|
||||
col_names.append(mc)
|
||||
params.append(self._wire(meta[mc]))
|
||||
|
||||
col_list = f"{self.embedding_col}, {self.text_col}"
|
||||
val_list = f"'{vec_str}', '{text.replace(\"'\", \"''\")}'"
|
||||
if meta_cols:
|
||||
col_list += ", " + ", ".join(meta_cols)
|
||||
val_list += ", " + ", ".join(meta_vals)
|
||||
|
||||
sql = f"INSERT INTO {self.table} ({col_list}) VALUES ({val_list}) RETURNING id"
|
||||
result = await self.client.query(sql)
|
||||
placeholders = [f"${j + 1}" for j in range(len(params))]
|
||||
sql = (
|
||||
f"INSERT INTO {self.table} ({', '.join(col_names)}) "
|
||||
f"VALUES ({', '.join(placeholders)}) RETURNING id"
|
||||
)
|
||||
result = await self.client.query_params(sql, params)
|
||||
if result.rows:
|
||||
inserted_ids.append(result.rows[0].get("id", str(i)))
|
||||
else:
|
||||
@@ -120,14 +132,36 @@ class BaraDBStore:
|
||||
|
||||
# Set tenant session variable if multi-tenant
|
||||
if self.tenant_id:
|
||||
await self.client.query(f"SET app.tenant_id = '{self.tenant_id}'")
|
||||
await self.client.query_params(
|
||||
"SET app.tenant_id = $1", [self._wire(self.tenant_id)]
|
||||
)
|
||||
|
||||
if filter_col and filter_val:
|
||||
sql = f"SELECT hybrid_search_filtered('{self.table}', '{self.embedding_col}', '{self.text_col}', '{query.replace(\"'\", \"''\")}', '{vec_str}', {k}, '{filter_col}', '{filter_val}') AS res"
|
||||
sql = (
|
||||
"SELECT hybrid_search_filtered($1, $2, $3, $4, $5, $6, $7, $8) AS res"
|
||||
)
|
||||
params = [
|
||||
self._wire(self.table),
|
||||
self._wire(self.embedding_col),
|
||||
self._wire(self.text_col),
|
||||
self._wire(query),
|
||||
self._wire(vec_str),
|
||||
self._wire(k),
|
||||
self._wire(filter_col),
|
||||
self._wire(filter_val),
|
||||
]
|
||||
else:
|
||||
sql = f"SELECT hybrid_search('{self.table}', '{self.embedding_col}', '{self.text_col}', '{query.replace(\"'\", \"''\")}', '{vec_str}', {k}) AS res"
|
||||
sql = "SELECT hybrid_search($1, $2, $3, $4, $5, $6) AS res"
|
||||
params = [
|
||||
self._wire(self.table),
|
||||
self._wire(self.embedding_col),
|
||||
self._wire(self.text_col),
|
||||
self._wire(query),
|
||||
self._wire(vec_str),
|
||||
self._wire(k),
|
||||
]
|
||||
|
||||
result = await self.client.query(sql)
|
||||
result = await self.client.query_params(sql, params)
|
||||
if not result.rows:
|
||||
return []
|
||||
|
||||
@@ -141,8 +175,11 @@ class BaraDBStore:
|
||||
for item in arr:
|
||||
doc_id = item.get("id", "")
|
||||
score = float(item.get("score", 0))
|
||||
# Fetch full row
|
||||
row_result = await self.client.query(f"SELECT * FROM {self.table} WHERE id = {doc_id}")
|
||||
# Fetch full row — use parameterized query
|
||||
row_result = await self.client.query_params(
|
||||
f"SELECT * FROM {self.table} WHERE id = $1",
|
||||
[self._wire(doc_id)],
|
||||
)
|
||||
if row_result.rows:
|
||||
page_content = row_result.rows[0].get(self.text_col, "")
|
||||
metadata = dict(row_result.rows[0])
|
||||
@@ -184,12 +221,19 @@ class BaraDBStore:
|
||||
async def delete(self, ids: Optional[List[str]] = None) -> None:
|
||||
await self._ensure_table()
|
||||
if ids:
|
||||
id_list = ", ".join(str(i) for i in ids)
|
||||
await self.client.query(f"DELETE FROM {self.table} WHERE id IN ({id_list})")
|
||||
# Build parameterized IN clause: $1, $2, ...
|
||||
placeholders = [f"${j + 1}" for j in range(len(ids))]
|
||||
params = [self._wire(i) for i in ids]
|
||||
await self.client.query_params(
|
||||
f"DELETE FROM {self.table} WHERE id IN ({', '.join(placeholders)})",
|
||||
params,
|
||||
)
|
||||
|
||||
async def set_tenant(self, tenant_id: str) -> None:
|
||||
self.tenant_id = tenant_id
|
||||
await self.client.query(f"SET app.tenant_id = '{tenant_id}'")
|
||||
await self.client.query_params(
|
||||
"SET app.tenant_id = $1", [self._wire(tenant_id)]
|
||||
)
|
||||
|
||||
|
||||
class _SimpleDocument:
|
||||
|
||||
Reference in New Issue
Block a user