mirror of
https://github.com/simonw/datasette.git
synced 2026-07-08 08:34:42 +02:00
Require permissions-debug for /-/threads
/-/threads exposes runtime internals - thread idents and asyncio task reprs including file paths - but only required view-instance. It now requires permissions-debug, like /-/actions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ
This commit is contained in:
parent
0bf3a54716
commit
e5e9aca871
8 changed files with 30 additions and 14 deletions
|
|
@ -2584,7 +2584,9 @@ class Datasette:
|
|||
r"/-/config(\.(?P<format>json))?$",
|
||||
)
|
||||
add_route(
|
||||
JsonDataView.as_view(self, "threads.json", self._threads),
|
||||
JsonDataView.as_view(
|
||||
self, "threads.json", self._threads, permission="permissions-debug"
|
||||
),
|
||||
r"/-/threads(\.(?P<format>json))?$",
|
||||
)
|
||||
add_route(
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ Without those query string arguments, the page lists up to five tables with dete
|
|||
/-/threads
|
||||
----------
|
||||
|
||||
Shows details of threads and ``asyncio`` tasks. `Threads example <https://latest.datasette.io/-/threads>`_:
|
||||
Shows details of threads and ``asyncio`` tasks. This endpoint requires the ``permissions-debug`` permission, since it exposes runtime internals. `Threads example <https://latest.datasette.io/-/threads>`_:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ value replaced by `"***"` (utils/__init__.py:1532-1556).
|
|||
### GET /-/threads(.json)
|
||||
|
||||
app.py:2566-2569, `Datasette._threads` (app.py:2268-2285). Permission
|
||||
`view-instance`. No parameters.
|
||||
**`permissions-debug`** (exposes runtime internals). No parameters.
|
||||
|
||||
Response: `num_threads`, `threads` (list of `{name, ident, daemon}`),
|
||||
`num_tasks`, `tasks` (asyncio task repr strings). When the
|
||||
|
|
|
|||
|
|
@ -306,9 +306,10 @@ Concerns:
|
|||
unauthorized actors get a uniform response.~~ ✅ **Done** — permission is
|
||||
checked first; the table schema view also now 404s (instead of a 500
|
||||
KeyError) for an unknown database.
|
||||
- **(P2) `/-/threads` exposes runtime internals** (thread idents, asyncio
|
||||
- ~~**(P2) `/-/threads` exposes runtime internals** (thread idents, asyncio
|
||||
task reprs including file paths) behind only `view-instance`. Consider
|
||||
`permissions-debug`, alongside `/-/actions` which already requires it.
|
||||
`permissions-debug`, alongside `/-/actions` which already requires it.~~
|
||||
✅ **Done** — `/-/threads` now requires `permissions-debug`.
|
||||
- **(P3) `/-/config` redaction is substring-based** on six key names
|
||||
(app.py:2502-2505); plugins storing secrets under other names leak. Worth
|
||||
a note in plugin authoring docs plus a `redact_keys` plugin hook.
|
||||
|
|
|
|||
|
|
@ -525,7 +525,11 @@ def test_databases_json(app_client_two_attached_databases_one_immutable):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_threads_json(ds_client):
|
||||
response = await ds_client.get("/-/threads.json")
|
||||
ds_client.ds.root_enabled = True
|
||||
try:
|
||||
response = await ds_client.get("/-/threads.json", actor={"id": "root"})
|
||||
finally:
|
||||
ds_client.ds.root_enabled = False
|
||||
expected_keys = {"ok", "threads", "num_threads"}
|
||||
if sys.version_info >= (3, 7, 0):
|
||||
expected_keys.update({"tasks", "num_tasks"})
|
||||
|
|
|
|||
|
|
@ -554,13 +554,9 @@ async def test_schema_endpoints_no_existence_oracle(tmp_path_factory):
|
|||
assert denied_existing.status_code == denied_missing.status_code == 403
|
||||
|
||||
# An authorized actor sees the real thing
|
||||
root_existing = await ds.client.get(
|
||||
"/data/-/schema.json", actor={"id": "root"}
|
||||
)
|
||||
root_existing = await ds.client.get("/data/-/schema.json", actor={"id": "root"})
|
||||
assert root_existing.status_code == 200
|
||||
root_missing = await ds.client.get(
|
||||
"/nope/-/schema.json", actor={"id": "root"}
|
||||
)
|
||||
root_missing = await ds.client.get("/nope/-/schema.json", actor={"id": "root"})
|
||||
assert root_missing.status_code == 404
|
||||
finally:
|
||||
ds.close()
|
||||
|
|
@ -604,3 +600,15 @@ async def test_unknown_extra_ignored_on_html_pages(ds_client):
|
|||
response = await ds_client.get("/fixtures/facetable?_extra=nope")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"].startswith("text/html")
|
||||
|
||||
|
||||
# /-/threads exposes runtime internals and requires permissions-debug
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_threads_requires_permissions_debug(ds_error_shape):
|
||||
denied = await ds_error_shape.client.get("/-/threads.json")
|
||||
assert_canonical_error(denied, 403)
|
||||
allowed = await ds_error_shape.client.get("/-/threads.json", actor={"id": "root"})
|
||||
assert allowed.status_code == 200
|
||||
assert allowed.json()["ok"] is True
|
||||
|
|
|
|||
|
|
@ -184,10 +184,11 @@ async def test_datasette_constructor():
|
|||
@pytest.mark.asyncio
|
||||
async def test_num_sql_threads_zero():
|
||||
ds = Datasette([], memory=True, settings={"num_sql_threads": 0})
|
||||
ds.root_enabled = True
|
||||
db = ds.add_database(Database(ds, memory_name="test_num_sql_threads_zero"))
|
||||
await db.execute_write("create table t(id integer primary key)")
|
||||
await db.execute_write("insert into t (id) values (1)")
|
||||
response = await ds.client.get("/-/threads.json")
|
||||
response = await ds.client.get("/-/threads.json", actor={"id": "root"})
|
||||
assert response.json() == {"ok": True, "num_threads": 0, "threads": []}
|
||||
response2 = await ds.client.get("/test_num_sql_threads_zero/t.json?_shape=array")
|
||||
assert response2.json() == [{"id": 1}]
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ def ds_envelope(tmp_path_factory):
|
|||
"/-/versions.json",
|
||||
"/-/settings.json",
|
||||
"/-/config.json",
|
||||
"/-/threads.json",
|
||||
"/-/actor.json",
|
||||
"/-/jump.json",
|
||||
"/-/schema.json",
|
||||
|
|
@ -58,6 +57,7 @@ async def test_success_object_has_ok_true(ds_client, path):
|
|||
(
|
||||
"/-/rules.json?action=view-instance",
|
||||
"/-/check.json?action=view-instance",
|
||||
"/-/threads.json",
|
||||
),
|
||||
)
|
||||
async def test_permission_debug_success_has_ok_true(ds_envelope, path):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue