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:
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.
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-KEYwhenADMIN_PASSis non-empty. IfADMIN_PASSis 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:
c7d80a62c2de95733c1e6c95a4eaedc50afaad75The admin middleware only enforces auth when
adminPassis non-empty:So an empty
ADMIN_PASSmeans no check is performed at all.The admin server exposes management routes and binds to
:8001:ADMIN_PASSis optional in configuration and has no default value:The local config sample sets it to an empty string:
The README also marks it as optional:
And the quick-start admin calls do not include an admin credential:
Why this matters
A user who deploys BricksLLM with Docker/compose and forgets
ADMIN_PASScan end up with a network-reachable management plane that allows unauthenticated callers to: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_PASSis set but the presented key is wrong, the middleware returns HTTP 200 with an empty body:This makes failed authentication look like success to scripts/monitors that only check status codes.
Suggested hardening
Possible fixes:
ADMIN_PASSwhen the admin server is enabled, or add an explicit opt-out such asALLOW_UNAUTHENTICATED_ADMIN=true;127.0.0.1:8001by default and require explicit configuration for0.0.0.0;401 Unauthorizedor403 Forbiddenfor missing/wrongX-API-KEYwhen admin auth is enabled;ADMIN_PASS/X-API-KEY, or clearly label the no-auth mode as local-only;ADMIN_PASSbehavior and wrong-key status codes.This would preserve the simple local setup while making accidental exposed deployments safer.