From 39dcebf39ab3cc01cc8d64bf71e7d5fa31ab208c Mon Sep 17 00:00:00 2001 From: woolcoxm Date: Tue, 14 Jul 2026 08:16:31 -0400 Subject: [PATCH] openai_server: constant-time API key check (hmac.compare_digest) + resource_plan: csv-parse GPU names (fixes comma-in-name drop) (#186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P6: discover_gpus() used line.split(',', 3) to parse nvidia-smi CSV output. If the GPU name contains a comma (e.g. 'Tesla, Inc. V100'), split produces more than 4 fields, the int() parse fails on the wrong field, and the GPU is silently dropped — doctor reports 'no NVIDIA device detected' with no clue why. Fixed by using the csv module (handles quoted fields correctly). P7: require_auth() used plain string != comparison for the API key ('Authorization' header vs expected 'Bearer '). This enables a timing side-channel that could leak the key byte-by-byte. Low impact when bound to localhost (default), but serve() only warns when host is non-localhost without a key, and users do expose on 0.0.0.0. Fixed by using hmac.compare_digest (constant-time comparison). Co-authored-by: woolcoxm <13604288+woolcoxm@users.noreply.github.com> --- c/openai_server.py | 10 +++++++--- c/resource_plan.py | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/c/openai_server.py b/c/openai_server.py index 1fa1669..a68e449 100644 --- a/c/openai_server.py +++ b/c/openai_server.py @@ -672,9 +672,13 @@ class APIHandler(BaseHTTPRequestHandler): self.send_header("Vary", "Origin") def require_auth(self): - if self.server.api_key and self.headers.get("Authorization") != f"Bearer {self.server.api_key}": - raise APIError(401, "Invalid or missing API key.", None, "invalid_api_key", - "authentication_error") + if self.server.api_key: + import hmac + provided = self.headers.get("Authorization", "") + expected = f"Bearer {self.server.api_key}" + if not hmac.compare_digest(provided, expected): + raise APIError(401, "Invalid or missing API key.", None, "invalid_api_key", + "authentication_error") def read_json(self): try: diff --git a/c/resource_plan.py b/c/resource_plan.py index b5571d3..33674e1 100644 --- a/c/resource_plan.py +++ b/c/resource_plan.py @@ -151,8 +151,9 @@ def discover_gpus(): except (OSError, subprocess.SubprocessError): return [] devices = [] - for line in result.stdout.splitlines(): - fields = [field.strip() for field in line.split(",", 3)] + import csv + for fields in csv.reader(result.stdout.splitlines()): + fields = [f.strip() for f in fields] if len(fields) != 4: continue try: