From 2c8e92acf26ae4096bcf0bfa921bd74252719ceb Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 26 Oct 2025 11:12:11 -0700 Subject: [PATCH] Require permissions-debug permission for /-/check endpoint The /-/check endpoint now requires the permissions-debug permission to access. This prevents unauthorized users from probing the permission system. Administrators can grant this permission to specific users or anonymous users if they want to allow open access. Added test to verify anonymous and regular users are denied access, while root user (who has all permissions) can access the endpoint. Closes #2546 --- datasette/views/special.py | 8 +------- tests/test_permissions.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/datasette/views/special.py b/datasette/views/special.py index 27454a37..b5ad2e8b 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -503,16 +503,10 @@ class PermissionCheckView(BaseView): has_json_alternate = False async def get(self, request): - # Check if user has permissions-debug (to show sensitive fields) - has_debug_permission = await self.ds.allowed( - action="permissions-debug", actor=request.actor - ) - - # Check if this is a request for JSON (has .json extension) + await self.ds.ensure_permission(action="permissions-debug", actor=request.actor) as_format = request.url_vars.get("format") if not as_format: - # Render the HTML form (even if query parameters are present) return await self.render( ["debug_check.html"], request, diff --git a/tests/test_permissions.py b/tests/test_permissions.py index afc67119..4d645704 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -1576,3 +1576,33 @@ async def test_actor_restrictions_parent_deny_blocks_config_child_allow(perms_ds # Clean up perms_ds._config = None + + +@pytest.mark.asyncio +async def test_permission_check_view_requires_debug_permission(): + """Test that /-/check requires permissions-debug permission""" + # Anonymous user should be denied + ds = Datasette() + response = await ds.client.get("/-/check.json?action=view-instance") + assert response.status_code == 403 + assert "permissions-debug" in response.text + + # User without permissions-debug should be denied + response = await ds.client.get( + "/-/check.json?action=view-instance", + cookies={"ds_actor": ds.sign({"id": "user"}, "actor")}, + ) + assert response.status_code == 403 + + # Root user should have access (root has all permissions) + ds_with_root = Datasette() + ds_with_root.root_enabled = True + root_token = ds_with_root.create_token("root") + response = await ds_with_root.client.get( + "/-/check.json?action=view-instance", + headers={"Authorization": f"Bearer {root_token}"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["action"] == "view-instance" + assert data["allowed"] is True