fix: escape SQL wildcards in tag search autocomplete
The % and _ characters in user search input are now escaped before the LIKE query, preventing unintended wildcard matching in tag search. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,8 +62,9 @@ async def search_tags(
|
|||||||
Searches tag names for the query string.
|
Searches tag names for the query string.
|
||||||
Returns matching tags ordered by usage count.
|
Returns matching tags ordered by usage count.
|
||||||
"""
|
"""
|
||||||
|
escaped_q = q.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
||||||
query = select(TreeTag).where(
|
query = select(TreeTag).where(
|
||||||
TreeTag.name.ilike(f"%{q}%")
|
TreeTag.name.ilike(f"%{escaped_q}%", escape="\\")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Filter by visibility
|
# Filter by visibility
|
||||||
|
|||||||
@@ -275,6 +275,37 @@ class TestTrees:
|
|||||||
)
|
)
|
||||||
assert len(tag_rows_after.fetchall()) == 0
|
assert len(tag_rows_after.fetchall()) == 0
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_tag_search_escapes_wildcards(
|
||||||
|
self, client: AsyncClient, admin_auth_headers: dict
|
||||||
|
):
|
||||||
|
"""Test that SQL wildcards in tag search are escaped, not interpreted."""
|
||||||
|
# Create tags as admin (can create global tags)
|
||||||
|
resp1 = await client.post(
|
||||||
|
"/api/v1/tags",
|
||||||
|
json={"name": "test_underscore"},
|
||||||
|
headers=admin_auth_headers
|
||||||
|
)
|
||||||
|
assert resp1.status_code == 201
|
||||||
|
|
||||||
|
resp2 = await client.post(
|
||||||
|
"/api/v1/tags",
|
||||||
|
json={"name": "testXunderscore"},
|
||||||
|
headers=admin_auth_headers
|
||||||
|
)
|
||||||
|
assert resp2.status_code == 201
|
||||||
|
|
||||||
|
# Search for literal underscore — should only match the first tag
|
||||||
|
response = await client.get(
|
||||||
|
"/api/v1/tags/search",
|
||||||
|
params={"q": "test_under"},
|
||||||
|
headers=admin_auth_headers
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
names = [t["name"] for t in response.json()]
|
||||||
|
assert "test_underscore" in names
|
||||||
|
assert "testXunderscore" not in names
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_tree_unauthorized(self, client: AsyncClient):
|
async def test_create_tree_unauthorized(self, client: AsyncClient):
|
||||||
"""Test that creating a tree without auth fails."""
|
"""Test that creating a tree without auth fails."""
|
||||||
|
|||||||
Reference in New Issue
Block a user