Datasette 1.0 alpha series leaks names of databases and tables to unauthenticated users
Description
Datasette is an open source multi-tool for exploring and publishing data. This bug affects Datasette instances running a Datasette 1.0 alpha - 1.0a0, 1.0a1, 1.0a2 or 1.0a3 - in an online accessible location but with authentication enabled using a plugin such as datasette-auth-passwords. The /-/api API explorer endpoint could reveal the names of both databases and tables - but not their contents - to an unauthenticated user. Datasette 1.0a4 has a fix for this issue. This will block access to the API explorer but will still allow access to the Datasette read or write JSON APIs, as those use different URL patterns within the Datasette /database hierarchy. This issue is patched in version 1.0a4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
datasettePyPI | >= 1.0a0, < 1.0a4 | 1.0a4 |
Affected products
1Patches
101e0558825b8Merge pull request from GHSA-7ch3-7pp7-7cpq
5 files changed · +99 −7
datasette/templates/api_explorer.html+1 −1 modified@@ -8,7 +8,7 @@ {% block content %} -<h1>API Explorer</h1> +<h1>API Explorer{% if private %} 🔒{% endif %}</h1> <p>Use this tool to try out the {% if datasette_version %}
datasette/version.py+1 −1 modified@@ -1,2 +1,2 @@ -__version__ = "1.0a3" +__version__ = "1.0a4" __version_info__ = tuple(__version__.split("."))
datasette/views/special.py+14 −5 modified@@ -354,9 +354,7 @@ async def example_links(self, request): if name == "_internal": continue database_visible, _ = await self.ds.check_visibility( - request.actor, - "view-database", - name, + request.actor, permissions=[("view-database", name), "view-instance"] ) if not database_visible: continue @@ -365,8 +363,11 @@ async def example_links(self, request): for table in table_names: visible, _ = await self.ds.check_visibility( request.actor, - "view-table", - (name, table), + permissions=[ + ("view-table", (name, table)), + ("view-database", name), + "view-instance", + ], ) if not visible: continue @@ -463,6 +464,13 @@ async def example_links(self, request): return databases async def get(self, request): + visible, private = await self.ds.check_visibility( + request.actor, + permissions=["view-instance"], + ) + if not visible: + raise Forbidden("You do not have permission to view this instance") + def api_path(link): return "/-/api#{}".format( urllib.parse.urlencode( @@ -480,5 +488,6 @@ def api_path(link): { "example_links": await self.example_links(request), "api_path": api_path, + "private": private, }, )
docs/changelog.rst+16 −0 modified@@ -4,6 +4,22 @@ Changelog ========= +.. _v1_0_a4: + +1.0a4 (2023-08-21) +------------------ + +This alpha fixes a security issue with the ``/-/api`` API explorer. On authenticated Datasette instances (instances protected using plugins such as `datasette-auth-passwords <https://datasette.io/plugins/datasette-auth-passwords>`__) the API explorer interface could reveal the names of databases and tables within the protected instance. The data stored in those tables was not revealed. + +For more information and workarounds, read `the security advisory <https://github.com/simonw/datasette/security/advisories/GHSA-7ch3-7pp7-7cpq>`__. The issue has been present in every previous alpha version of Datasette 1.0: versions 1.0a0, 1.0a1, 1.0a2 and 1.0a3. + +Also in this alpha: + +- The new ``datasette plugins --requirements`` option outputs a list of currently installed plugins in Python ``requirements.txt`` format, useful for duplicating that installation elsewhere. (:issue:`2133`) +- :ref:`canned_queries_writable` can now define a ``on_success_message_sql`` field in their configuration, containing a SQL query that should be executed upon successful completion of the write operation in order to generate a message to be shown to the user. (:issue:`2138`) +- The automatically generated border color for a database is now shown in more places around the application. (:issue:`2119`) +- Every instance of example shell script code in the documentation should now include a working copy button, free from additional syntax. (:issue:`2140`) + .. _v1_0_a3: 1.0a3 (2023-08-09)
tests/test_permissions.py+67 −0 modified@@ -53,6 +53,7 @@ async def perms_ds(): ( "/", "/fixtures", + "/-/api", "/fixtures/compound_three_primary_keys", "/fixtures/compound_three_primary_keys/a,a,a", "/fixtures/two", # Query @@ -951,3 +952,69 @@ def test_cli_create_token(options, expected): ) assert 0 == result2.exit_code, result2.output assert json.loads(result2.output) == {"actor": expected} + + +_visible_tables_re = re.compile(r">\/((\w+)\/(\w+))\.json<\/a> - Get rows for") + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "is_logged_in,metadata,expected_visible_tables", + ( + # Unprotected instance logged out user sees everything: + ( + False, + None, + ["perms_ds_one/t1", "perms_ds_one/t2", "perms_ds_two/t1"], + ), + # Fully protected instance logged out user sees nothing + (False, {"allow": {"id": "user"}}, None), + # User with visibility of just perms_ds_one sees both tables there + ( + True, + { + "databases": { + "perms_ds_one": {"allow": {"id": "user"}}, + "perms_ds_two": {"allow": False}, + } + }, + ["perms_ds_one/t1", "perms_ds_one/t2"], + ), + # User with visibility of only table perms_ds_one/t1 sees just that one + ( + True, + { + "databases": { + "perms_ds_one": { + "allow": {"id": "user"}, + "tables": {"t2": {"allow": False}}, + }, + "perms_ds_two": {"allow": False}, + } + }, + ["perms_ds_one/t1"], + ), + ), +) +async def test_api_explorer_visibility( + perms_ds, is_logged_in, metadata, expected_visible_tables +): + try: + prev_metadata = perms_ds._metadata_local + perms_ds._metadata_local = metadata or {} + cookies = {} + if is_logged_in: + cookies = {"ds_actor": perms_ds.client.actor_cookie({"id": "user"})} + response = await perms_ds.client.get("/-/api", cookies=cookies) + if expected_visible_tables: + assert response.status_code == 200 + # Search HTML for stuff matching: + # '>/perms_ds_one/t2.json</a> - Get rows for' + visible_tables = [ + match[0] for match in _visible_tables_re.findall(response.text) + ] + assert visible_tables == expected_visible_tables + else: + assert response.status_code == 403 + finally: + perms_ds._metadata_local = prev_metadata
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-7ch3-7pp7-7cpqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-40570ghsaADVISORY
- github.com/pypa/advisory-database/tree/main/vulns/datasette/PYSEC-2023-154.yamlghsaWEB
- github.com/simonw/datasette/commit/01e0558825b8f7ec17d3b691aa072daf122fcc74ghsax_refsource_MISCWEB
- github.com/simonw/datasette/security/advisories/GHSA-7ch3-7pp7-7cpqghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.