From fb6217635814b341f5a7c09c9118c80b2db62e48 Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Thu, 2 Jul 2026 17:31:19 +0400 Subject: [PATCH] admin: constant-time credential comparison --- lib/admin_auth.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/admin_auth.rb b/lib/admin_auth.rb index c7af28e..acb9ea2 100644 --- a/lib/admin_auth.rb +++ b/lib/admin_auth.rb @@ -1,4 +1,5 @@ require 'rack/auth/basic' +require 'rack/utils' # Rack middleware that gates everything under /admin/* behind HTTP Basic. # Credentials come from ENV at boot; missing vars fail closed (401 always). @@ -14,7 +15,7 @@ class AdminAuth return @app.call(env) unless path == '/admin' || path.start_with?('/admin/') auth = Rack::Auth::Basic::Request.new(env) - if @user && @pass && auth.provided? && auth.basic? && auth.credentials == [@user, @pass] + if @user && @pass && auth.provided? && auth.basic? && credentials_match?(auth.credentials) @app.call(env) else [401, @@ -22,4 +23,13 @@ class AdminAuth ["Unauthorized\n"]] end end + + private + + def credentials_match?(creds) + user, pass = creds + # Single & so both comparisons always run (no short-circuit timing signal). + Rack::Utils.secure_compare(@user, user.to_s) & + Rack::Utils.secure_compare(@pass, pass.to_s) + end end