From 42a205b4bdb84fbccb154a0a68ea7aaf9a97d8b7 Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 23 Sep 2026 00:55:15 -0400 Subject: [PATCH] Admin auth: constant-time panel-token comparison in require_admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- api/auth.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/auth.py b/api/auth.py index 816f649..c4b0bc1 100644 --- a/api/auth.py +++ b/api/auth.py @@ -48,6 +48,7 @@ from __future__ import annotations import asyncio import binascii import json +import secrets import time from collections.abc import Awaitable, Callable from typing import Annotated @@ -459,7 +460,14 @@ async def require_admin( if ( config is not None 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( status_code=403,