Skip to content

Commit 5e77d64

Browse files
JSLEEKRclaude
andcommitted
fix: sanitize SQL comment/label to prevent comment injection
The comment() and label() methods passed user input directly into SQL comments (/* ... */) without stripping */ sequences, allowing comment breakout and potential SQL injection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cb1267d commit 5e77d64

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

src/sqlink/builder.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
from sqlink.types import JoinType, OrderDirection
1212

1313

14+
def _sanitize_comment(text: str) -> str:
15+
"""Strip comment-closing sequences to prevent SQL comment injection."""
16+
return text.replace("*/", "* /").replace("/*", "/ *")
17+
18+
1419
@dataclass
1520
class _JoinClause:
1621
join_type: JoinType
@@ -95,12 +100,12 @@ def dialect(self, d: Dialect) -> Query:
95100

96101
def comment(self, text: str) -> Query:
97102
"""Add a SQL comment to the query (/* ... */)."""
98-
self._comment = text
103+
self._comment = _sanitize_comment(text)
99104
return self
100105

101106
def label(self, name: str) -> Query:
102107
"""Add a label/tag comment (/* app:label */) for query tracing."""
103-
self._label = name
108+
self._label = _sanitize_comment(name)
104109
return self
105110

106111
# ── SELECT ──────────────────────────────────────────────

tests/test_comments.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,17 @@ def test_label_preserved_in_clone(self):
9090
def test_no_comment_by_default(self):
9191
sql, _ = Query("users").select("*").build()
9292
assert "/*" not in sql
93+
94+
def test_comment_injection_sanitized(self):
95+
"""Prevent SQL injection via comment-closing sequences."""
96+
sql, _ = Query("users").select("*").comment("evil */ DROP TABLE users; /*").build()
97+
# The closing */ should only appear once (at the end of the comment)
98+
assert sql.count("*/") == 1
99+
# The opening /* should only appear once (at the start of the comment)
100+
assert sql.count("/*") == 1
101+
102+
def test_label_injection_sanitized(self):
103+
"""Prevent SQL injection via label with comment-closing sequences."""
104+
sql, _ = Query("users").select("*").label("evil */ DROP TABLE users; /*").build()
105+
assert sql.count("*/") == 1
106+
assert sql.count("/*") == 1

0 commit comments

Comments
 (0)