VYPR
Moderate severityNVD Advisory· Published Aug 25, 2023· Updated Oct 2, 2024

Datasette 1.0 alpha series leaks names of databases and tables to unauthenticated users

CVE-2023-40570

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.

PackageAffected versionsPatched versions
datasettePyPI
>= 1.0a0, < 1.0a41.0a4

Affected products

1

Patches

1
01e0558825b8

Merge pull request from GHSA-7ch3-7pp7-7cpq

https://github.com/simonw/datasetteSimon WillisonAug 22, 2023via ghsa
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

News mentions

0

No linked articles in our index yet.