Admin auth: constant-time panel-token comparison in require_admin

Fixes review finding P2: require_admin compared the Bearer credential
against config.panel_token with Python ==, which short-circuits and is
not constant-time. Compare with secrets.compare_digest on UTF-8 bytes
instead — the same pattern require_panel_token already uses for this
exact secret — keeping the truthiness guard on config.panel_token and
the same 403 rejection detail and behaviour.
This commit is contained in:
2026-09-23 00:55:15 -04:00
parent 4d697dc025
commit 42a205b4bd
+9 -1
View File
@@ -48,6 +48,7 @@ from __future__ import annotations
import asyncio import asyncio
import binascii import binascii
import json import json
import secrets
import time import time
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from typing import Annotated from typing import Annotated
@@ -459,7 +460,14 @@ async def require_admin(
if ( if (
config is not None config is not None
and config.panel_token and config.panel_token
and credential.strip() == config.panel_token # compare_digest on bytes (as require_panel_token does)
# so the refusal does not short-circuit on the first
# mismatching byte — a constant-time comparison keeps a
# timing side channel on the credential off the table.
and secrets.compare_digest(
credential.strip().encode("utf-8"),
config.panel_token.encode("utf-8"),
)
): ):
raise HTTPException( raise HTTPException(
status_code=403, status_code=403,