Skip to content

Admin API fails open when ADMIN_PASS is unset and uses 200 for failed auth #106

Description

@chenshj73

Admin API fails open when ADMIN_PASS is unset and uses 200 for failed auth

Hi, thanks for BricksLLM. I saw the earlier discussions in #42 and #93 about securing the Admin API with ADMIN_PASS. I wanted to report a narrower default-hardening issue that still seems present in the current code.

The Admin API only checks X-API-KEY when ADMIN_PASS is non-empty. If ADMIN_PASS is unset or empty, the middleware lets every admin request through. At the same time, the admin server listens on :8001, and the quick-start docs create provider settings and Bricks API keys without any admin authentication header.

For an LLM gateway, the admin surface is sensitive because it can configure upstream provider credentials, create gateway API keys, manage routes/policies/users, and read reporting/log data.

Evidence

Current commit inspected: c7d80a62c2de95733c1e6c95a4eaedc50afaad75

The admin middleware only enforces auth when adminPass is non-empty:

11: func getAdminLoggerMiddleware(log *zap.Logger, prefix string, prod bool, adminPass string) gin.HandlerFunc {
12:     return func(c *gin.Context) {
13:         if len(adminPass) != 0 && c.Request.Header.Get("X-API-KEY") != adminPass {
14:             c.Status(200)
15:             c.Abort()
16:             return
17:         }

So an empty ADMIN_PASS means no check is performed at all.

The admin server exposes management routes and binds to :8001:

76:  router.POST("/api/v2/key-management/keys", getGetKeysV2Handler(m, prod))
77:  router.GET("/api/key-management/keys", getGetKeysHandler(m, prod))
78:  router.PUT("/api/key-management/keys", getCreateKeyHandler(m, prod))
79:  router.PATCH("/api/key-management/keys/:id", getUpdateKeyHandler(m, prod))
80:  router.DELETE("/api/key-management/keys/:id", getDeleteKeyHandler(m, prod))
...
92:  router.PUT("/api/provider-settings", getCreateProviderSettingHandler(psm, prod))
93:  router.GET("/api/provider-settings", getGetProviderSettingsHandler(psm, prod))
94:  router.PATCH("/api/provider-settings/:id", getUpdateProviderSettingHandler(psm, prod))
...
100: router.POST("/api/routes", getCreateRouteHandler(rm, prod))
101: router.GET("/api/routes/:id", getGetRouteHandler(rm, prod))
102: router.GET("/api/routes", getGetRoutesHandler(rm, prod))
103: router.DELETE("/api/routes/:id", getDeleteRouteHandler(rm, prod))
...
109: router.POST("/api/users", getCreateUserHandler(um, prod))
110: router.PATCH("/api/users/:id", getUpdateUserHandler(um, prod))
111: router.PATCH("/api/users", getUpdateUserViaTagsAndUserIdHandler(um, prod))
112: router.GET("/api/users", getGetUsersHandler(um, prod))
...
114: srv := &http.Server{
115:     Addr:    ":8001",
116:     Handler: router,
117: }

ADMIN_PASS is optional in configuration and has no default value:

40: AdminPass string `koanf:"admin_pass" env:"ADMIN_PASS"`

The local config sample sets it to an empty string:

22:     "admin_pass": "",

The README also marks it as optional:

162: > | `ADMIN_PASS`         | optional | Simple password for the admin server. |

And the quick-start admin calls do not include an admin credential:

68: ### Step 4 - Create a provider setting
69: ```bash
70: curl -X PUT http://localhost:8001/api/provider-settings \
71:    -H "Content-Type: application/json" \
...
81: ### Step 5 - Create a Bricks API key
82: Use `id` from the previous step as `settingId` to create a key with a rate limit of 2 req/min and a spend limit of 25 cents.
83: ```bash
84: curl -X PUT http://localhost:8001/api/key-management/keys \
85:    -H "Content-Type: application/json" \

Why this matters

A user who deploys BricksLLM with Docker/compose and forgets ADMIN_PASS can end up with a network-reachable management plane that allows unauthenticated callers to:

  • create or modify provider settings, including upstream provider configuration;
  • create, update, or delete Bricks API keys;
  • modify routes and policies;
  • view users and reporting/event data.

That can lead to unauthorized usage of configured upstream provider accounts, quota/cost consumption, route/policy tampering, and possible exposure of operational or user request data.

There is also a smaller interoperability issue: when ADMIN_PASS is set but the presented key is wrong, the middleware returns HTTP 200 with an empty body:

13: if len(adminPass) != 0 && c.Request.Header.Get("X-API-KEY") != adminPass {
14:     c.Status(200)
15:     c.Abort()
16:     return
17: }

This makes failed authentication look like success to scripts/monitors that only check status codes.

Suggested hardening

Possible fixes:

  • require ADMIN_PASS when the admin server is enabled, or add an explicit opt-out such as ALLOW_UNAUTHENTICATED_ADMIN=true;
  • bind the admin server to 127.0.0.1:8001 by default and require explicit configuration for 0.0.0.0;
  • return 401 Unauthorized or 403 Forbidden for missing/wrong X-API-KEY when admin auth is enabled;
  • update the quick-start examples to include ADMIN_PASS/X-API-KEY, or clearly label the no-auth mode as local-only;
  • add a regression test covering empty ADMIN_PASS behavior and wrong-key status codes.

This would preserve the simple local setup while making accidental exposed deployments safer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions