Search before asking
What happened
With AUTH_ENABLED=true (native OIDC, #8854), every non-public API route accepts any Authorization: Basic <user>:<pass> header as an authenticated user. Nothing checks the password.
OAuth2ProxyAuthentication (backend/server/api/middlewares.go) predates native auth. It decodes the Basic header and sets common.USER to the username, because in the classic layout nginx in config-ui has already verified the credentials against ADMIN_USER/ADMIN_PASS before proxying to the lake, and the lake only needed the name for audit fields:
func getBasicAuthUserInfo(c *gin.Context, basicRes context.BasicRes) (*common.User, error) {
...
userInfo := strings.Split(string(userInfoData), ":")
if len(userInfo) != 2 {
return nil, errors.Default.New("invalid user info data")
}
return &common.User{Name: userInfo[0]}, nil
}
#8854 added auth.RequireAuth() right after it (backend/server/api/api.go):
router.Use(RestAuthentication(router, basicRes))
router.Use(RequirePushAuthentication(basicRes))
router.Use(auth.OIDCAuthentication())
router.Use(OAuth2ProxyAuthentication(basicRes))
router.Use(auth.RequireAuth())
RequireAuth only asks whether a user is set, so the unverified Basic username now satisfies the gate. #8880 closed the same hole for X-Forwarded-User by requiring FORWARDED_USER_SECRET, but left the Basic branch as is.
Any deployment that exposes the lake directly with AUTH_ENABLED=true (the documented setup for native OIDC, where config-ui runs without ADMIN_USER/ADMIN_PASS) is affected: blueprints, connections (with stored tokens), pipelines and API keys are reachable and writable without signing in. CSRFProtect does not apply either, since the request carries no session cookie.
What do you expect to happen
With AUTH_ENABLED=true the lake is the authenticator. Only a valid session cookie, a valid API key, or forwarded headers with the matching FORWARDED_USER_SECRET should produce a user. A Basic header the lake cannot verify must not.
How to reproduce
Lake with AUTH_ENABLED=true, OIDC_ENABLED=true, one provider configured, reachable directly (not through config-ui nginx):
curl -s -o /dev/null -w '%{http_code}\n' "$LAKE/blueprints?page=1&pageSize=1"
# 401
curl -s -o /dev/null -w '%{http_code}\n' -u 'anyone:anything' "$LAKE/blueprints?page=1&pageSize=1"
# 200, full blueprint list
curl -s -o /dev/null -w '%{http_code}\n' -H 'Authorization: Bearer nope' "$LAKE/blueprints?page=1&pageSize=1"
# 401
Authorization: Basic Og== (empty user) and a non-base64 value give 401, so it is the decoded username alone that authenticates.
Anything else
Every time, on every non-public route. Verified on v1.0.3-beta17 (8fe26f4); middlewares.go is unchanged on main. Present since v1.0.3-beta13, the first release with #8854.
Fix I propose: consult the Basic header only when AUTH_ENABLED is off, which is the legacy nginx mode. With AUTH_ENABLED the supported callers are already what auth.go logs: "only API-key/proxy auth will work". Deployments that still front the lake with nginx Basic auth and also set AUTH_ENABLED=true can move to FORWARDED_USER_SECRET. A 9-line change plus tests; the bypass test fails on current code. Running as a local patch on our instance since 2026-09-17.
Version
v1.0.3-beta17 (8fe26f4), same code on main.
Are you willing to submit PR?
Code of Conduct
Search before asking
What happened
With
AUTH_ENABLED=true(native OIDC, #8854), every non-public API route accepts anyAuthorization: Basic <user>:<pass>header as an authenticated user. Nothing checks the password.OAuth2ProxyAuthentication(backend/server/api/middlewares.go) predates native auth. It decodes the Basic header and setscommon.USERto the username, because in the classic layout nginx in config-ui has already verified the credentials againstADMIN_USER/ADMIN_PASSbefore proxying to the lake, and the lake only needed the name for audit fields:#8854 added
auth.RequireAuth()right after it (backend/server/api/api.go):RequireAuthonly asks whether a user is set, so the unverified Basic username now satisfies the gate. #8880 closed the same hole forX-Forwarded-Userby requiringFORWARDED_USER_SECRET, but left the Basic branch as is.Any deployment that exposes the lake directly with
AUTH_ENABLED=true(the documented setup for native OIDC, where config-ui runs withoutADMIN_USER/ADMIN_PASS) is affected: blueprints, connections (with stored tokens), pipelines and API keys are reachable and writable without signing in.CSRFProtectdoes not apply either, since the request carries no session cookie.What do you expect to happen
With
AUTH_ENABLED=truethe lake is the authenticator. Only a valid session cookie, a valid API key, or forwarded headers with the matchingFORWARDED_USER_SECRETshould produce a user. A Basic header the lake cannot verify must not.How to reproduce
Lake with
AUTH_ENABLED=true,OIDC_ENABLED=true, one provider configured, reachable directly (not through config-ui nginx):Authorization: Basic Og==(empty user) and a non-base64 value give 401, so it is the decoded username alone that authenticates.Anything else
Every time, on every non-public route. Verified on
v1.0.3-beta17(8fe26f4);middlewares.gois unchanged onmain. Present sincev1.0.3-beta13, the first release with #8854.Fix I propose: consult the Basic header only when
AUTH_ENABLEDis off, which is the legacy nginx mode. WithAUTH_ENABLEDthe supported callers are already whatauth.gologs: "only API-key/proxy auth will work". Deployments that still front the lake with nginx Basic auth and also setAUTH_ENABLED=truecan move toFORWARDED_USER_SECRET. A 9-line change plus tests; the bypass test fails on current code. Running as a local patch on our instance since 2026-09-17.Version
v1.0.3-beta17 (8fe26f4), same code on
main.Are you willing to submit PR?
Code of Conduct