mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 10:04:45 +00:00
fix(security): require auth for /cloud/callback (GHSA-p66q-2gfp-8v55) (#4863)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -131,3 +131,33 @@ func Test_createRoutes_simple_bad_password(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, rr.Code, 401, "Response code should be 401.")
|
assert.Equal(t, rr.Code, 401, "Response code should be 401.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_createRoutes_simple_cloud_callback_requires_auth(t *testing.T) {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
|
||||||
|
|
||||||
|
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/",
|
||||||
|
Authorization: Authorization{
|
||||||
|
Provider: SIMPLE,
|
||||||
|
Authorizer: auth.NewSimpleAuth(auth.UserDatabase{
|
||||||
|
Users: map[string]*auth.User{
|
||||||
|
"amir": {
|
||||||
|
Username: "amir",
|
||||||
|
Password: "$2a$10$4Tvzu0ms9shlv4B8pIfqI.TM9CoqsamsAznP91A1NGuwg/68SGS1m",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, time.Second*100),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// The cloud callback must not be reachable without authentication. Otherwise
|
||||||
|
// an attacker could force-link the instance to their own cloud account.
|
||||||
|
for _, path := range []string{"/api/cloud/callback?token=attacker", "/api/cloud/status"} {
|
||||||
|
req, err := http.NewRequest("GET", path, nil)
|
||||||
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 401, rr.Code, "%s should require authentication.", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ func createRouter(h *handler) *chi.Mux {
|
|||||||
r.Patch("/cloud/config", h.updateCloudConfig)
|
r.Patch("/cloud/config", h.updateCloudConfig)
|
||||||
r.Delete("/cloud/config", h.deleteCloudConfig)
|
r.Delete("/cloud/config", h.deleteCloudConfig)
|
||||||
r.Post("/cloud/feedback", h.cloudFeedback)
|
r.Post("/cloud/feedback", h.cloudFeedback)
|
||||||
|
// Cloud callback handles the OAuth-style code exchange. It must stay
|
||||||
|
// authenticated so an unauthenticated attacker cannot force-link the
|
||||||
|
// instance to their own cloud account via SetCloudConfig.
|
||||||
|
r.Get("/cloud/callback", h.cloudCallback)
|
||||||
|
|
||||||
// MCP (Model Context Protocol) endpoint
|
// MCP (Model Context Protocol) endpoint
|
||||||
if h.config.EnableMCP {
|
if h.config.EnableMCP {
|
||||||
@@ -229,9 +233,6 @@ func createRouter(h *handler) *chi.Mux {
|
|||||||
r.Post("/token", h.createToken)
|
r.Post("/token", h.createToken)
|
||||||
r.Delete("/token", h.deleteToken)
|
r.Delete("/token", h.deleteToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cloud callback (public, handles OAuth-style code exchange)
|
|
||||||
r.Get("/cloud/callback", h.cloudCallback)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Get("/healthcheck", h.healthcheck)
|
r.Get("/healthcheck", h.healthcheck)
|
||||||
|
|||||||
Reference in New Issue
Block a user