diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 713b11074..7442f3442 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -3378,7 +3378,9 @@ def enable_fts( table_fts=quote_identifier(self.name + "_fts"), columns=", ".join(quote_identifier(c) for c in columns), fts_version=fts_version, - tokenize=f"\n tokenize='{tokenize}'," if tokenize else "", + tokenize=( + f"\n tokenize={self.db.quote(tokenize)}," if tokenize else "" + ), ) ) should_recreate = False diff --git a/tests/test_fts.py b/tests/test_fts.py index 50c1770b8..395fc662f 100644 --- a/tests/test_fts.py +++ b/tests/test_fts.py @@ -252,6 +252,18 @@ def test_fts_tokenize(fresh_db, fts_version): }.items() <= rows[0].items() +def test_fts_tokenize_escaped(fresh_db): + # A malicious tokenize value must not be able to break out of the + # string literal in the CREATE VIRTUAL TABLE statement. + table = fresh_db["searchable"] + table.insert_all(search_records) + malicious = "porter'); CREATE TABLE injected(x); --" + with pytest.raises(Exception): + table.enable_fts(["text"], tokenize=malicious) + # The injected statement must not have executed + assert "injected" not in fresh_db.table_names() + + def test_optimize_fts(fresh_db): for fts_version in ("4", "5"): table_name = f"searchable_{fts_version}"