Skip to content

Security: adminbook HTTP Basic fails OPEN if admin credentials are unset (blank:blank authenticates) #85

Description

@zacgoodwin

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions