Skip to content

Commit ee32821

Browse files
committed
SQL: Attempt to use cratedb-sqlparse for implementing read-only mode
1 parent b94242f commit ee32821

File tree

3 files changed

+41
-8
lines changed

3 files changed

+41
-8
lines changed

cratedb_mcp/knowledge.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ruff: noqa: E501
22
import logging
33

4+
import cratedb_sqlparse
45
import sqlparse
56

67
from cratedb_mcp.settings import PERMIT_ALL_STATEMENTS
@@ -131,25 +132,55 @@ def sql_expression_permitted(expression: str) -> bool:
131132

132133
def _sql_expression_permitted(expression: str) -> bool:
133134

135+
if expression:
136+
expression = expression.strip()
137+
134138
if not expression:
135139
return False
136140

137141
if PERMIT_ALL_STATEMENTS:
138142
return True
139143

140144
# Parse the SQL statement.
141-
parsed = sqlparse.parse(expression.strip())
142-
if not parsed:
143-
return False
145+
parsed = cratedb_sqlparse.sqlparse(expression)
144146

145-
# Check for multiple statements (potential SQL injection).
147+
# Reject multiple statements to prevent potential SQL injections.
146148
if len(parsed) > 1:
147149
return False
148150

149151
# Check if the expression is valid and if it's a SELECT statement,
150152
# also trying to consider `SELECT ... INTO ...` statements.
151-
operation = parsed[0].get_type().upper()
153+
operation = parsed[0].type.upper()
154+
is_select = operation == 'SELECT'
155+
is_rejected = _sql_is_select_into(expression) or _sql_is_evasive(expression)
156+
if is_select and not is_rejected:
157+
return True
158+
return False
159+
160+
161+
def _sql_is_select_into(expression: str) -> bool:
162+
"""
163+
Helper function using traditional `sqlparse` for catching `SELECT ... INTO ...` statements.
164+
165+
Examples:
166+
167+
SELECT * INTO foobar FROM bazqux
168+
SELECT * FROM bazqux INTO foobar
169+
"""
170+
parsed = sqlparse.parse(expression)
152171
tokens = [str(item).upper() for item in parsed[0]]
153-
if operation != 'SELECT' or (operation == 'SELECT' and 'INTO' in tokens):
154-
return False
155-
return True
172+
return "INTO" in tokens
173+
174+
175+
def _sql_is_evasive(expression: str) -> bool:
176+
"""
177+
Helper function using traditional `sqlparse` for catching evasive SQL statements.
178+
179+
Reject multiple statements to prevent potential SQL injections.
180+
181+
Examples:
182+
183+
SELECT * FROM users; \uff1b DROP TABLE users
184+
"""
185+
parsed = sqlparse.parse(expression)
186+
return len(parsed) > 1

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dynamic = [
2121
]
2222
dependencies = [
2323
"attrs",
24+
"cratedb-sqlparse==0.0.14",
2425
"hishel<0.2",
2526
"mcp[cli]>=1.5.0",
2627
"sqlparse<0.6",

tests/test_knowledge.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def test_sql_expression_insert_rejected():
5555
def test_sql_expression_select_into_rejected():
5656
"""SELECT+DML statements are rejected"""
5757
assert sql_expression_permitted("SELECT * INTO foobar FROM bazqux") is False
58+
assert sql_expression_permitted("SELECT * FROM bazqux INTO foobar") is False
5859

5960

6061
def test_sql_expression_empty_rejected():

0 commit comments

Comments
 (0)