diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index ddd9df8d..47ca0551 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -187,7 +187,9 @@ allowed_pragmas = ( disallawed_sql_res = [ ( re.compile(f"pragma(?!_({'|'.join(allowed_pragmas)}))"), - "Statement may not contain PRAGMA", + "Statement contained a disallowed PRAGMA. Allowed pragma functions are {}".format( + ", ".join("pragma_{}()".format(pragma) for pragma in allowed_pragmas) + ), ) ] diff --git a/docs/sql_queries.rst b/docs/sql_queries.rst index 74e1e4e5..93f17eaf 100644 --- a/docs/sql_queries.rst +++ b/docs/sql_queries.rst @@ -50,10 +50,7 @@ SQLite string escaping rules will be applied to values passed using named parameters - they will be wrapped in quotes and their content will be correctly escaped. -Datasette disallows custom SQL containing the string PRAGMA, as SQLite pragma -statements can be used to change database settings at runtime. If you need to -include the string "pragma" in a query you can do so safely using a named -parameter. +Datasette disallows custom SQL queries containing the string PRAGMA (with a small number `of exceptions `__) as SQLite pragma statements can be used to change database settings at runtime. If you need to include the string "pragma" in a query you can do so safely using a named parameter. .. _sql_views: diff --git a/tests/test_html.py b/tests/test_html.py index e1bcf4d3..c7dd9d97 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1,4 +1,5 @@ from bs4 import BeautifulSoup as Soup +from datasette.utils import allowed_pragmas from .fixtures import ( # noqa app_client, app_client_base_url_prefix, @@ -124,6 +125,20 @@ def test_invalid_custom_sql(app_client): assert "Statement must be a SELECT" in response.text +def test_disallowed_custom_sql_pragma(app_client): + response = app_client.get( + "/fixtures?sql=SELECT+*+FROM+pragma_not_on_allow_list('idx52')" + ) + assert response.status == 400 + pragmas = ", ".join("pragma_{}()".format(pragma) for pragma in allowed_pragmas) + assert ( + "Statement contained a disallowed PRAGMA. Allowed pragma functions are {}".format( + pragmas + ) + in response.text + ) + + def test_sql_time_limit(app_client_shorter_time_limit): response = app_client_shorter_time_limit.get("/fixtures?sql=select+sleep(0.5)") assert 400 == response.status