mirror of
https://github.com/simonw/datasette.git
synced 2026-09-21 16:04:07 +02:00
Fix for GHSA-h547-rmjf-5m2m
This commit is contained in:
parent
36eac40b5d
commit
23b76098f6
4 changed files with 52 additions and 1 deletions
|
|
@ -343,7 +343,7 @@ def escape_css_string(s):
|
|||
|
||||
|
||||
def escape_sqlite(s):
|
||||
if _boring_keyword_re.match(s) and (s.lower() not in reserved_words):
|
||||
if _boring_keyword_re.fullmatch(s) and (s.lower() not in reserved_words):
|
||||
return s
|
||||
elif "]" in s:
|
||||
# SQLite does not support escaping ] inside [bracket] quoting, so fall
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
.. _v0_65_5:
|
||||
|
||||
0.65.5 (2026-09-16)
|
||||
-------------------
|
||||
|
||||
- Fixed a security issue where a trailing newline in a requested table name could bypass table permissions and expose private rows. Thanks for the report, `dpfkdlemtp <https://github.com/dpfkdlemtp>`__. `GHSA-h547-rmjf-5m2m <https://github.com/simonw/datasette/security/advisories/GHSA-h547-rmjf-5m2m>`__
|
||||
|
||||
.. _v0_65_4:
|
||||
|
||||
0.65.4 (2026-09-10)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,48 @@ from datasette.app import Datasette
|
|||
from datasette.plugins import pm
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("create_decoy", [False, True])
|
||||
@pytest.mark.parametrize("path", [".json", "/1.json"])
|
||||
async def test_trailing_lf_table_permissions(tmp_path, create_decoy, path):
|
||||
db_path = tmp_path / "data.db"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.executescript(
|
||||
"create table secret (id integer primary key, value text);"
|
||||
"insert into secret values (1, 'private');"
|
||||
)
|
||||
if create_decoy:
|
||||
conn.executescript(
|
||||
'create table "secret\n" (id integer primary key, value text);'
|
||||
"insert into \"secret\n\" values (1, 'decoy');"
|
||||
)
|
||||
conn.close()
|
||||
ds = Datasette(
|
||||
[db_path],
|
||||
settings={"default_allow_sql": False},
|
||||
metadata={"databases": {"data": {"tables": {"secret": {"allow": False}}}}},
|
||||
)
|
||||
try:
|
||||
response = await ds.client.get("/data.json?sql=select+*+from+secret")
|
||||
assert response.status_code == 403
|
||||
response = await ds.client.get("/data/secret" + path)
|
||||
assert response.status_code == 403
|
||||
|
||||
# A trailing line feed must not bypass the protected table's permissions.
|
||||
# Row URLs must also be safe when the suffixed table does not exist.
|
||||
response = await ds.client.get("/data/secret~0A" + path + "?_shape=array")
|
||||
if create_decoy:
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"id": 1, "value": "decoy"}]
|
||||
elif path == ".json":
|
||||
assert response.status_code == 404
|
||||
else:
|
||||
assert response.status_code == 400
|
||||
assert response.json()["error"] == "no such table: secret\n"
|
||||
finally:
|
||||
ds.executor.shutdown(wait=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ds(tmp_path):
|
||||
path = tmp_path / "catalog.db"
|
||||
|
|
|
|||
|
|
@ -216,6 +216,8 @@ def test_detect_fts(open_quote, close_quote):
|
|||
"identifier,expected",
|
||||
(
|
||||
("plain", "plain"),
|
||||
("plain\n", "[plain\n]"),
|
||||
("select\n", "[select\n]"),
|
||||
("select", "[select]"),
|
||||
("has space", "[has space]"),
|
||||
("has]bracket", '"has]bracket"'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue