fix: reject trailing newline in collection-name validation

Python's $ anchor matches just before a final newline, so a $-anchored
re.match(r'^[a-zA-Z0-9_-]{1,128}$', name) accepted "papers\n". In local
mode get_or_create_collection() then hit SQLite's CHECK via
INSERT OR IGNORE, silently created no row, and returned a Collection that
failed later on add(). Switch all three duplicated validators (local,
cloud, sqlite backends) to re.fullmatch() so the whole string must match.
This commit is contained in:
mountain 2026-07-09 19:12:56 +08:00
parent e00d360273
commit 91e016d2e4
5 changed files with 26 additions and 5 deletions

View file

@ -188,6 +188,17 @@ def test_delete_collection_rejects_path_traversal(backend, tmp_path):
assert canary.exists()
@pytest.mark.parametrize("bad_name", ["papers\n", "\npapers", "papers\n\n"])
def test_get_or_create_collection_rejects_trailing_newline(backend, bad_name):
# Regression: Python's $ matches just before a final \n, so a $-anchored
# .match() accepted "papers\n"; get_or_create_collection then hit the SQL
# CHECK via INSERT OR IGNORE, silently created no row, and handed back a
# Collection that failed later on add(). .fullmatch() rejects it up front.
from pageindex.errors import PageIndexError
with pytest.raises(PageIndexError, match="Invalid collection name"):
backend.get_or_create_collection(bad_name)
def test_add_document_missing_file_raises_file_not_found(backend, tmp_path):
backend.get_or_create_collection("papers")
with pytest.raises(FileNotFoundError):

View file

@ -36,6 +36,10 @@ def test_create_duplicate_collection_raises_pageindex_error(storage):
@pytest.mark.parametrize("bad_name", [
"a/../../etc/passwd", "/etc/passwd", "a$(whoami)", ".hidden",
"a b", "válid", "", "a" * 129,
# A trailing newline must be rejected: Python's $ matches just before a
# final \n, so a $-anchored .match() would let "papers\n" slip through
# (then INSERT OR IGNORE silently no-ops on the SQL CHECK).
"papers\n", "\npapers", "papers\n\n",
])
def test_create_collection_rejects_invalid_names_at_the_python_layer(storage, bad_name):
"""SQLiteStorage must validate collection names itself — it's a public