Context
Adminbook::BaseController gates the admin panel with HTTP Basic auth only in
production/ru_production:
http_basic_authenticate_with name: Rails.application.credentials.admin&.fetch(:username, '') || '',
password: Rails.application.credentials.admin&.fetch(:password, '') || '',
if: -> { Rails.env.production? || Rails.env.ru_production? }
The &.fetch(..., '') || '' fallback fails open, not closed. If the admin
credentials key is ever absent (never set, removed, or a botched rotation),
name/password resolve to "" — and http_basic_authenticate_with name: "", password: "" authenticates a blank blank:blank credential rather than
denying it.
Verified (2026-07-22):
- Live prod is currently SAFE:
admin.username/admin.password are set in the
encrypted credentials, so /adminbook returns 401 to unauthenticated, blank
(Authorization: Basic Og==), and garbage creds. No live exposure.
- Local rack probe with
http_basic_authenticate_with name: "", password: "":
no header -> 401, but Basic Og== (blank:blank) -> 200, x:y -> 401.
Blank config authenticates blank creds. secure_compare("", "") is true.
So the only thing standing between "secured" and "world-open adminbook in
production" is the continued presence of the admin credentials key. That is a
fragile invariant for an admin panel that can edit all TLC content.
Plan
app/controllers/adminbook/base_controller.rb: fail closed when admin creds
are unset. Simplest: raise on boot / deny all if credentials.admin is nil in
a production env, instead of configuring blank Basic auth. E.g. resolve creds
once, and if either is blank in production, register a before_action that
always head :service_unavailable (or raise at class-eval) rather than
installing http_basic_authenticate_with with empty strings.
- Keep dev/test behavior unchanged (adminbook is intentionally open there — the
if: guard already scopes auth to prod).
Acceptance Criteria
- Setup: production env,
Rails.application.credentials.admin returns nil.
Action: request /adminbook with Authorization: Basic Og== (blank:blank).
Expected: NOT 200 — denied (401 or 503). Today it would be 200.
- Setup: production env, admin creds set to real values. Action: request with
correct creds. Expected: 200 (unchanged). Action: request blank/garbage.
Expected: 401 (unchanged).
- Setup: test env (no creds). Action: request
/adminbook. Expected: reachable,
no auth prompt (unchanged — if: guard).
Tests + evals
- Request spec
spec/requests/adminbook/auth_spec.rb: the three AC cases above,
stubbing Rails.application.credentials.admin nil vs set and forcing
Rails.env.production?. The blank-creds-nil case is the one that would have
caught this (it goes 200 today).
Docs
- README adminbook note + CLAUDE.md credentials note: state that
admin
username/password are REQUIRED for production and the panel fails closed
without them (currently the docs assume they exist but nothing enforces it).
Out of scope
- Moving adminbook off HTTP Basic to a real session/role model (separate, larger).
- Rate limiting / IP allowlist on the admin panel.
Context
Adminbook::BaseControllergates the admin panel with HTTP Basic auth only inproduction/ru_production:
The
&.fetch(..., '') || ''fallback fails open, not closed. If theadmincredentials key is ever absent (never set, removed, or a botched rotation),
name/passwordresolve to""— andhttp_basic_authenticate_with name: "", password: ""authenticates a blankblank:blankcredential rather thandenying it.
Verified (2026-07-22):
admin.username/admin.passwordare set in theencrypted credentials, so
/adminbookreturns 401 to unauthenticated, blank(
Authorization: Basic Og==), and garbage creds. No live exposure.http_basic_authenticate_with name: "", password: "":no header -> 401, but
Basic Og==(blank:blank) -> 200,x:y-> 401.Blank config authenticates blank creds.
secure_compare("", "")istrue.So the only thing standing between "secured" and "world-open adminbook in
production" is the continued presence of the
admincredentials key. That is afragile invariant for an admin panel that can edit all TLC content.
Plan
app/controllers/adminbook/base_controller.rb: fail closed when admin credsare unset. Simplest: raise on boot / deny all if
credentials.adminis nil ina production env, instead of configuring blank Basic auth. E.g. resolve creds
once, and if either is blank in production, register a before_action that
always
head :service_unavailable(or raise at class-eval) rather thaninstalling
http_basic_authenticate_withwith empty strings.if:guard already scopes auth to prod).Acceptance Criteria
Rails.application.credentials.adminreturns nil.Action: request
/adminbookwithAuthorization: Basic Og==(blank:blank).Expected: NOT 200 — denied (401 or 503). Today it would be 200.
correct creds. Expected: 200 (unchanged). Action: request blank/garbage.
Expected: 401 (unchanged).
/adminbook. Expected: reachable,no auth prompt (unchanged —
if:guard).Tests + evals
spec/requests/adminbook/auth_spec.rb: the three AC cases above,stubbing
Rails.application.credentials.adminnil vs set and forcingRails.env.production?. The blank-creds-nil case is the one that would havecaught this (it goes 200 today).
Docs
adminusername/password are REQUIRED for production and the panel fails closed
without them (currently the docs assume they exist but nothing enforces it).
Out of scope