From 4241a4b09ef3f93a05fe03cc8afbb8bca76398b0 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Thu, 5 Feb 2026 14:57:35 +0100 Subject: [PATCH] Security fixes for RLM. Fix type() builtin, AST validation for dict key access, sql injection --- TOOLS_REFACTORING_PLAN.md | 200 ++++++++++++++++++ .../haiku/rag/agents/rlm/sandbox.py | 25 ++- haiku_rag_slim/haiku/rag/client.py | 8 +- .../haiku/rag/store/repositories/document.py | 9 +- tests/agents/rlm/test_sandbox.py | 131 ++++++++++++ ...ql_injection_is_blocked_with_escaping.yaml | 82 +++++++ ...sql_injection_in_get_document_blocked.yaml | 82 +++++++ tests/test_client.py | 47 ++++ 8 files changed, 574 insertions(+), 10 deletions(-) create mode 100644 TOOLS_REFACTORING_PLAN.md create mode 100644 tests/cassettes/test_client/test_sql_injection_is_blocked_with_escaping.yaml create mode 100644 tests/cassettes/test_sandbox/TestSandboxEscapeVectors.test_sql_injection_in_get_document_blocked.yaml diff --git a/TOOLS_REFACTORING_PLAN.md b/TOOLS_REFACTORING_PLAN.md new file mode 100644 index 00000000..9e4dfae6 --- /dev/null +++ b/TOOLS_REFACTORING_PLAN.md @@ -0,0 +1,200 @@ +# Tools Extraction Refactoring Plan + +## Goal + +Extract tools from haiku.rag agents into a reusable `tools/` module, enabling users to create pydantic-ai agents outside haiku.rag and compose toolsets as needed. + +## Target API + +```python +from pydantic_ai import Agent +from haiku.rag import HaikuRAG +from haiku.rag.tools import ToolContext, create_search_toolset, create_document_toolset + +async with HaikuRAG(db_path) as client: + context = ToolContext() + search_tools = create_search_toolset(client, config, context) + doc_tools = create_document_toolset(client, config, context) + + agent = Agent( + 'anthropic:claude-sonnet', + toolsets=[search_tools, doc_tools] + ) + result = await agent.run("Find documents about X") + + # Access accumulated state after run + search_state = context.get("haiku.rag.search") + for result in search_state.results: + print(f"{result.document_title}") +``` + +## Design Principles + +1. **ToolContext is a pure generic container** - No special-cased fields. Toolsets register their own Pydantic model state under namespaces. + +2. **Shared state via same namespace** - Multiple toolsets can share state (e.g., citations, filters) by registering under the same namespace. + +3. **App manages identity** - ToolContext has no session/user identity. The app layer manages `session_id -> ToolContext` mapping. + +4. **Toolsets are stateless factories** - `create_*_toolset()` returns a `FunctionToolset`. State lives in the context they're given. + +## ToolContext Design + +```python +class ToolContext(BaseModel): + """Generic state container for toolsets. + + Toolsets register Pydantic model state under namespaces. + Multiple toolsets can share state via the same namespace. + """ + _namespaces: dict[str, BaseModel] = PrivateAttr(default_factory=dict) + + def register(self, namespace: str, state: BaseModel) -> None: ... + def get(self, namespace: str) -> BaseModel | None: ... + def get_or_create(self, namespace: str, factory: Callable[[], T]) -> T: ... + def clear_namespace(self, namespace: str) -> None: ... + def clear_all(self) -> None: ... + def dump_namespaces(self) -> dict[str, dict[str, Any]]: ... + def load_namespace(self, namespace: str, state_type: type[T], data: dict) -> T: ... +``` + +## Toolset State Examples + +Each toolset defines its own state model: + +```python +# Search toolset state +class SearchState(BaseModel): + results: list[SearchResult] = [] + filter: str | None = None + +SEARCH_NAMESPACE = "haiku.rag.search" + +# QA toolset state +class QAState(BaseModel): + history: list[QAResult] = [] + +QA_NAMESPACE = "haiku.rag.qa" + +# Shared citation state (used by multiple toolsets) +class CitationState(BaseModel): + registry: dict[str, int] = {} + + def get_or_assign_index(self, chunk_id: str) -> int: + if chunk_id in self.registry: + return self.registry[chunk_id] + new_index = len(self.registry) + 1 + self.registry[chunk_id] = new_index + return new_index + +CITATION_NAMESPACE = "haiku.rag.citations" +``` + +## Multi-User/Session Management + +App layer manages context routing: + +```python +# App maintains context per session +contexts: dict[str, ToolContext] = {} + +def get_context(session_id: str) -> ToolContext: + if session_id not in contexts: + contexts[session_id] = ToolContext() + return contexts[session_id] + +# When running agent +context = get_context(user_session_id) +toolsets = [create_search_toolset(client, config, context)] +await agent.run(prompt, toolsets=toolsets) +``` + +## New Module Structure + +``` +haiku_rag_slim/haiku/rag/ +├── tools/ # NEW +│ ├── __init__.py # Public exports +│ ├── context.py # ToolContext (generic state container) +│ ├── models.py # QAResult, AnalysisResult +│ ├── filters.py # build_document_filter, combine_filters +│ ├── search.py # create_search_toolset() +│ ├── document.py # create_document_toolset() +│ ├── qa.py # create_qa_toolset() +│ └── analysis.py # create_analysis_toolset() +├── agents/ # REFACTORED to use tools/ +``` + +## Implementation Chunks + +### Chunk 1: Create tools module foundation ✅ DONE +- Created `tools/__init__.py`, `tools/context.py`, `tools/models.py`, `tools/filters.py` +- Created `ToolContext` as generic namespace-based Pydantic model +- Moved filter utilities from `agents/chat/state.py` to `tools/filters.py` +- Created result models (`QAResult`, `AnalysisResult`) +- Added tests for ToolContext and filters + +### Chunk 2: Create SearchToolset ✅ DONE +- Created `tools/search.py` with `create_search_toolset()` +- Defined `SearchState` model for accumulating search results +- Core search logic: `client.search()` → `client.expand_context()` → `format_for_agent()` +- Results accumulated in `SearchState` under `SEARCH_NAMESPACE` +- Added 13 tests for SearchToolset + +### Chunk 3: Refactor QA Agent to use SearchToolset ✅ DONE +- Updated `agents/qa/agent.py` to use `create_search_toolset()` +- Added `base_filter` and `tool_name` parameters to `create_search_toolset()` +- QA agent now uses ToolContext + SearchState for result accumulation +- Public interface (`answer(question, filter)`) unchanged +- All 5 QA tests pass + +### Chunk 4: Create DocumentToolset ✅ DONE +- Created `tools/document.py` with `create_document_toolset()` +- Defined `DocumentState`, `DocumentInfo`, `DocumentListResponse` models +- Extracted `list_documents`, `get_document`, `summarize_document` tools +- Moved `find_document` helper (now public) +- Added 13 tests + +### Chunk 5: Create QAToolset ✅ DONE +- Created `tools/qa.py` with `create_qa_toolset()` +- Defined `QAState` model (tracks QA history) +- Runs research graph, returns structured `QAResult` +- Supports `base_filter`, `tool_name`, `session_context`, `prior_answers` params +- Added 7 tests + +### Chunk 6: Create AnalysisToolset ✅ DONE +- Created `tools/analysis.py` with `create_analysis_toolset()` +- Defined `AnalysisState` model (tracks CodeExecution history) +- Extracted `analyze` tool (RLM delegation with filter support) +- Fixed circular import by using direct submodule imports +- Added 6 tests + +### Chunk 7: Refactor Chat Agent ✅ DONE +- Removed `analyze` tool from chat agent (kept hardcoded, not composing toolsets) +- Reverted system prompt to pre-analyze version +- Removed `test_analyze_tool` test and cassette file +- All 47 chat agent tests pass + +### Chunk 8: Refactor Research Graph +- Update `_search_one_step_logic` to use search toolset +- Verify research tests pass + +### Chunk 9: Public API and Documentation +- Export from `haiku.rag.tools` and `haiku.rag` +- Update CLAUDE.md +- Add usage examples + +## Verification + +- Run `pytest` after each chunk +- Run `ty check` and `ruff check` +- Test with existing agents (QA, Chat, Research) +- Test with external agent using new toolsets + +## Critical Files + +- `haiku_rag_slim/haiku/rag/agents/chat/agent.py` - largest tool collection +- `haiku_rag_slim/haiku/rag/agents/qa/agent.py` - simplest, good starting point +- `haiku_rag_slim/haiku/rag/agents/chat/state.py` - filter utilities (now moved) +- `haiku_rag_slim/haiku/rag/agents/research/graph.py` - search tool inside step +- `haiku_rag_slim/haiku/rag/store/models/chunk.py` - SearchResult.format_for_agent() diff --git a/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py b/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py index 6db78141..8691988e 100644 --- a/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py +++ b/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any from haiku.rag.agents.rlm.dependencies import RLMContext from haiku.rag.config.models import RLMConfig +from haiku.rag.store.repositories.document import _escape_sql_string if TYPE_CHECKING: from haiku.rag.client import HaikuRAG @@ -84,7 +85,9 @@ class REPLEnvironment: "str": str, "sum": sum, "tuple": tuple, - "type": type, + "type": ( + lambda obj: type(obj) + ), # Single-arg only, blocks type(name, bases, dict) "zip": zip, "Exception": Exception, "ValueError": ValueError, @@ -206,13 +209,14 @@ class REPLEnvironment: doc = await self.client.get_document_by_id(id_or_title) if doc: return doc.content + safe_input = _escape_sql_string(id_or_title) docs = await self.client.list_documents( - filter=f"title = '{id_or_title}'" + filter=f"title = '{safe_input}'" ) if docs and docs[0].id: full_doc = await self.client.get_document_by_id(docs[0].id) return full_doc.content if full_doc else None - docs = await self.client.list_documents(filter=f"uri = '{id_or_title}'") + docs = await self.client.list_documents(filter=f"uri = '{safe_input}'") if docs and docs[0].id: full_doc = await self.client.get_document_by_id(docs[0].id) return full_doc.content if full_doc else None @@ -230,13 +234,14 @@ class REPLEnvironment: doc = await self.client.get_document_by_id(id_or_title) if doc: return doc.get_docling_document() + safe_input = _escape_sql_string(id_or_title) docs = await self.client.list_documents( - filter=f"title = '{id_or_title}'" + filter=f"title = '{safe_input}'" ) if docs and docs[0].id: full_doc = await self.client.get_document_by_id(docs[0].id) return full_doc.get_docling_document() if full_doc else None - docs = await self.client.list_documents(filter=f"uri = '{id_or_title}'") + docs = await self.client.list_documents(filter=f"uri = '{safe_input}'") if docs and docs[0].id: full_doc = await self.client.get_document_by_id(docs[0].id) return full_doc.get_docling_document() if full_doc else None @@ -305,6 +310,16 @@ class REPLEnvironment: raise SecurityError( f"Access to private/dunder attribute '{node.attr}' is not allowed" ) + # Block dictionary key access to dunder/private strings + # This prevents type.__dict__['__subclasses__'] attacks + if isinstance(node, ast.Subscript): + if isinstance(node.slice, ast.Constant): + if isinstance( + node.slice.value, str + ) and node.slice.value.startswith("_"): + raise SecurityError( + f"Dictionary access to '{node.slice.value}' is not allowed" + ) def _execute_sync(self, code: str) -> REPLResult: """Internal synchronous execution - must be called from executor thread.""" diff --git a/haiku_rag_slim/haiku/rag/client.py b/haiku_rag_slim/haiku/rag/client.py index 6d39c62e..8fb3e7d5 100644 --- a/haiku_rag_slim/haiku/rag/client.py +++ b/haiku_rag_slim/haiku/rag/client.py @@ -22,7 +22,10 @@ from haiku.rag.store.engine import Store from haiku.rag.store.models.chunk import Chunk, SearchResult from haiku.rag.store.models.document import Document from haiku.rag.store.repositories.chunk import ChunkRepository -from haiku.rag.store.repositories.document import DocumentRepository +from haiku.rag.store.repositories.document import ( + DocumentRepository, + _escape_sql_string, +) from haiku.rag.store.repositories.settings import SettingsRepository if TYPE_CHECKING: @@ -1322,7 +1325,8 @@ class HaikuRAG: for doc_ref in documents: doc = await self.get_document_by_id(doc_ref) if not doc: - docs = await self.list_documents(filter=f"title = '{doc_ref}'") + safe_ref = _escape_sql_string(doc_ref) + docs = await self.list_documents(filter=f"title = '{safe_ref}'") if docs and docs[0].id: doc = await self.get_document_by_id(docs[0].id) if doc: diff --git a/haiku_rag_slim/haiku/rag/store/repositories/document.py b/haiku_rag_slim/haiku/rag/store/repositories/document.py index 9f45a1fb..dfc01594 100644 --- a/haiku_rag_slim/haiku/rag/store/repositories/document.py +++ b/haiku_rag_slim/haiku/rag/store/repositories/document.py @@ -77,9 +77,10 @@ class DocumentRepository: async def get_by_id(self, entity_id: str) -> Document | None: """Get a document by its ID.""" + safe_id = _escape_sql_string(entity_id) results = list( self.store.documents_table.search() - .where(f"id = '{entity_id}'") + .where(f"id = '{safe_id}'") .limit(1) .to_pydantic(DocumentRecord) ) @@ -104,8 +105,9 @@ class DocumentRepository: entity.updated_at = datetime.fromisoformat(now) # Update the record + safe_id = _escape_sql_string(entity.id) self.store.documents_table.update( - where=f"id = '{entity.id}'", + where=f"id = '{safe_id}'", values={ "content": entity.content, "uri": entity.uri, @@ -136,7 +138,8 @@ class DocumentRepository: await self.chunk_repository.delete_by_document_id(entity_id) # Delete the document - self.store.documents_table.delete(f"id = '{entity_id}'") + safe_id = _escape_sql_string(entity_id) + self.store.documents_table.delete(f"id = '{safe_id}'") return True async def list_all( diff --git a/tests/agents/rlm/test_sandbox.py b/tests/agents/rlm/test_sandbox.py index 249f376a..88eb9a07 100644 --- a/tests/agents/rlm/test_sandbox.py +++ b/tests/agents/rlm/test_sandbox.py @@ -1,6 +1,13 @@ +from pathlib import Path + import pytest +@pytest.fixture(scope="module") +def vcr_cassette_dir(): + return str(Path(__file__).parent.parent.parent / "cassettes" / "test_sandbox") + + class TestSafeBuiltins: """Test that safe builtins are available.""" @@ -572,6 +579,130 @@ class TestPreloadedDocuments: assert "test://doc" in result.stdout +class TestSandboxEscapeVectors: + """Test that known sandbox escape techniques are blocked. + + Each test contains actual exploit code that would work without the fix. + """ + + @pytest.mark.asyncio + async def test_type_dict_subclasses_escape_blocked(self, repl_env_empty): + """Cannot escape via type.__dict__['__subclasses__']. + + Without fix: This would enumerate all loaded classes and find + subprocess.Popen to execute arbitrary shell commands. + """ + result = await repl_env_empty.execute_async(""" +# EXPLOIT: Access __subclasses__ via dict to bypass AST check +subclasses_method = type.__dict__['__subclasses__'] +all_classes = subclasses_method(object) +print(f"Found {len(all_classes)} classes") +""") + assert not result.success + assert "not allowed" in result.stderr.lower() + + @pytest.mark.asyncio + async def test_popen_shell_execution_blocked(self, repl_env_empty): + """Cannot execute shell commands via Popen. + + Without fix: This would execute 'whoami' and return the username. + """ + result = await repl_env_empty.execute_async(""" +# EXPLOIT: Find subprocess.Popen and execute shell commands +subclasses_method = type.__dict__['__subclasses__'] +all_classes = subclasses_method(object) +popen = [c for c in all_classes if c.__name__ == 'Popen'][0] +proc = popen('whoami', shell=True, stdout=-1) +print(proc.stdout.read()) +""") + assert not result.success + + @pytest.mark.asyncio + async def test_socket_creation_blocked(self, repl_env_empty): + """Cannot create network sockets for data exfiltration. + + Without fix: This would create a socket that could connect to external servers. + """ + result = await repl_env_empty.execute_async(""" +# EXPLOIT: Find socket class and create network connection +subclasses_method = type.__dict__['__subclasses__'] +all_classes = subclasses_method(object) +socket_cls = [c for c in all_classes if c.__name__ == 'socket'][0] +s = socket_cls(2, 1) # AF_INET, SOCK_STREAM +print(f"Created socket: {s}") +""") + assert not result.success + + @pytest.mark.asyncio + async def test_type_three_arg_class_creation_blocked(self, repl_env_empty): + """Cannot use type() with 3 arguments to create classes dynamically.""" + result = await repl_env_empty.execute_async( + "EvilClass = type('EvilClass', (object,), {'x': 1})" + ) + assert not result.success + + @pytest.mark.asyncio + async def test_dict_key_dunder_access_blocked(self, repl_env_empty): + """Cannot access dunder methods via dictionary key access.""" + result = await repl_env_empty.execute_async( + "method = str.__dict__['__add__']\nprint(method)" + ) + assert not result.success + assert "not allowed" in result.stderr.lower() + + @pytest.mark.asyncio + async def test_dict_key_private_access_blocked(self, repl_env_empty): + """Cannot access private attributes via dictionary key access.""" + result = await repl_env_empty.execute_async( + "method = object.__dict__['_private']\nprint(method)" + ) + assert not result.success + assert "not allowed" in result.stderr.lower() + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_sql_injection_in_get_document_blocked(self, temp_db_path): + """SQL injection in get_document cannot bypass context filter. + + Without fix: Injecting quotes would leak documents that should be + protected by the context filter. + """ + from haiku.rag.agents.rlm.dependencies import RLMContext + from haiku.rag.agents.rlm.sandbox import REPLEnvironment + from haiku.rag.client import HaikuRAG + from haiku.rag.config.models import RLMConfig + + async with HaikuRAG(temp_db_path, create=True) as client: + # Create documents: one secret, one public + await client.create_document( + content="TOP SECRET: Launch codes 1234", + uri="secret://classified", + title="Classified Intel", + ) + await client.create_document( + content="Public weather report", + uri="public://weather", + title="Weather", + ) + + # Sandbox restricted to public:// only + context = RLMContext(filter="uri LIKE 'public://%'") + repl = REPLEnvironment(client=client, config=RLMConfig(), context=context) + + # EXPLOIT: SQL injection to access secret document + result = await repl.execute_async(""" +# Injection payload breaks out of quotes and adds OR clause +content = get_document("x' OR uri LIKE 'secret://%") +if content: + print(f"LEAKED: {content}") +else: + print("NO LEAK") +""") + assert result.success + assert "TOP SECRET" not in result.stdout + assert "Launch codes" not in result.stdout + + class TestSecurityEscapes: """Test that common security escape attempts are blocked.""" diff --git a/tests/cassettes/test_client/test_sql_injection_is_blocked_with_escaping.yaml b/tests/cassettes/test_client/test_sql_injection_is_blocked_with_escaping.yaml new file mode 100644 index 00000000..5160d904 --- /dev/null +++ b/tests/cassettes/test_client/test_sql_injection_is_blocked_with_escaping.yaml @@ -0,0 +1,82 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '96' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Secret classified data XYZ + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 5 + total_tokens: 5 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '97' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Public report about weather + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 5 + total_tokens: 5 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_sandbox/TestSandboxEscapeVectors.test_sql_injection_in_get_document_blocked.yaml b/tests/cassettes/test_sandbox/TestSandboxEscapeVectors.test_sql_injection_in_get_document_blocked.yaml new file mode 100644 index 00000000..a2a27516 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxEscapeVectors.test_sql_injection_in_get_document_blocked.yaml @@ -0,0 +1,82 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '99' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - 'TOP SECRET: Launch codes 1234' + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 11 + total_tokens: 11 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '91' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Public weather report + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 4 + total_tokens: 4 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index 0a56e36f..33644f79 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1384,3 +1384,50 @@ async def test_client_convert_with_html_format(temp_db_path): labels = [str(getattr(item, "label", "")) for item, _ in items] assert "title" in labels + + +@pytest.mark.asyncio +@pytest.mark.vcr() +async def test_sql_injection_is_blocked_with_escaping(temp_db_path): + """SQL injection is blocked when using _escape_sql_string. + + This test verifies that _escape_sql_string properly prevents SQL injection + by escaping single quotes in user input. + """ + from haiku.rag.store.repositories.document import _escape_sql_string + + async with HaikuRAG(temp_db_path, create=True) as client: + # Create documents + await client.create_document( + content="Secret classified data XYZ", + uri="secret://doc", + title="Secret", + ) + await client.create_document( + content="Public report about weather", + uri="public://report", + title="Weather Report", + ) + + # Without escaping, this injection would match all documents + # by breaking out of the string literal: title = 'x' OR title LIKE '%' + injection_payload = "x' OR title LIKE '%" + + # With proper escaping, single quotes become double quotes + # so the filter becomes: title = 'x'' OR title LIKE ''%' + # which searches for a literal title containing the injection string + safe_payload = _escape_sql_string(injection_payload) + docs = await client.list_documents(filter=f"title = '{safe_payload}'") + + # Should find 0 documents (injection is escaped, searching for literal string) + assert len(docs) == 0 + + # Verify the escaping works correctly + assert safe_payload == "x'' OR title LIKE ''%" + + # Verify unescaped injection would have matched documents (for test validity) + # This demonstrates that the injection works without escaping + docs_unescaped = await client.list_documents( + filter=f"title = '{injection_payload}'" + ) + assert len(docs_unescaped) == 2 # SQL injection succeeds without escaping