Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/artifact-cas/internal/server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func NewHTTPServer(c *conf.Server, authConf *conf.Auth, downloadSvc *service.Dow
logging.Server(logger),
),
}
// Stop unmatched routes from falling through to http.DefaultServeMux,
// which would expose /debug/vars and /debug/requests (CVE-2026-6993).
opts = append(opts, middlewares_http.DenyDefaultMuxFallthrough()...)
if c.Http.Network != "" {
opts = append(opts, http.Network(c.Http.Network))
}
Expand Down
5 changes: 4 additions & 1 deletion app/artifact-cas/internal/server/httpmetrics.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@ package server

import (
"github.com/chainloop-dev/chainloop/app/artifact-cas/internal/conf"
middlewares_http "github.com/chainloop-dev/chainloop/pkg/middlewares/http"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand All @@ -34,6 +35,8 @@ func NewHTTPMetricsServer(c *conf.Server) (*HTTPMetricsServer, error) {
recovery.Recovery(),
),
}
// Stop unmatched routes from falling through to http.DefaultServeMux (CVE-2026-6993).
opts = append(opts, middlewares_http.DenyDefaultMuxFallthrough()...)

if c.HttpMetrics.Network != "" {
opts = append(opts, http.Network(c.HttpMetrics.Network))
Expand Down
3 changes: 3 additions & 0 deletions app/controlplane/internal/server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func NewHTTPServer(opts *Opts, grpcSrv *grpc.Server) (*http.Server, error) {
var serverOpts = []http.ServerOption{
http.Middleware(middlewares...),
}
// Stop unmatched routes from falling through to http.DefaultServeMux,
// which would expose /debug/pprof, /debug/vars and /debug/requests (CVE-2026-6993).
serverOpts = append(serverOpts, middlewares_http.DenyDefaultMuxFallthrough()...)

if v := opts.ServerConfig.Http.Network; v != "" {
serverOpts = append(serverOpts, http.Network(v))
Expand Down
5 changes: 4 additions & 1 deletion app/controlplane/internal/server/httpmetrics.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@
package server

import (
middlewares_http "github.com/chainloop-dev/chainloop/pkg/middlewares/http"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
Expand All @@ -28,6 +29,8 @@ type HTTPMetricsServer struct {
// NewHTTPMetricsServer exposes the metrics endpoint in another port
func NewHTTPMetricsServer(opts *Opts) (*HTTPMetricsServer, error) {
var serverOpts = []http.ServerOption{}
// Stop unmatched routes from falling through to http.DefaultServeMux (CVE-2026-6993).
serverOpts = append(serverOpts, middlewares_http.DenyDefaultMuxFallthrough()...)

if v := opts.ServerConfig.HttpMetrics.Network; v != "" {
serverOpts = append(serverOpts, http.Network(v))
Expand Down
45 changes: 45 additions & 0 deletions pkg/middlewares/http/denymux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Copyright 2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package http

import (
nhttp "net/http"

"github.com/go-kratos/kratos/v2/transport/http"
)

// DenyDefaultMuxFallthrough returns Kratos server options that stop the HTTP
// server from routing unmatched requests to http.DefaultServeMux.
//
// By default Kratos sets the router's NotFoundHandler and MethodNotAllowedHandler
// to http.DefaultServeMux (CVE-2026-6993, CWE-441). Packages such as
// net/http/pprof, expvar and golang.org/x/net/trace auto-register handlers on
// that global mux from their init() functions, so any unmatched route on a
// public server leaks /debug/pprof/*, /debug/vars and /debug/requests.
//
// Returning a plain 404/405 instead severs that fallthrough on every network
// path. Registered routes are matched by the router and never reach these
// handlers, so legitimate endpoints are unaffected.
func DenyDefaultMuxFallthrough() []http.ServerOption {
return []http.ServerOption{
http.NotFoundHandler(nhttp.HandlerFunc(func(w nhttp.ResponseWriter, _ *nhttp.Request) {
w.WriteHeader(nhttp.StatusNotFound)
})),
http.MethodNotAllowedHandler(nhttp.HandlerFunc(func(w nhttp.ResponseWriter, _ *nhttp.Request) {
w.WriteHeader(nhttp.StatusMethodNotAllowed)
})),
}
}
80 changes: 80 additions & 0 deletions pkg/middlewares/http/denymux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright 2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package http

import (
nhttp "net/http"
"net/http/httptest"
"testing"

"github.com/go-kratos/kratos/v2/transport/http"
"github.com/stretchr/testify/assert"
)

// register a sentinel handler on the global mux to emulate what net/http/pprof,
// expvar and x/net/trace do from their init() functions.
func init() {
nhttp.DefaultServeMux.HandleFunc("/debug/sentinel", func(w nhttp.ResponseWriter, _ *nhttp.Request) {
w.WriteHeader(nhttp.StatusOK)
_, _ = w.Write([]byte("LEAKED"))
})
}

func TestDenyDefaultMuxFallthrough(t *testing.T) {
testCases := []struct {
name string
opts []http.ServerOption
path string
wantStatus int
}{
{
name: "without the option the request falls through to DefaultServeMux",
opts: nil,
path: "/debug/sentinel",
wantStatus: nhttp.StatusOK,
},
{
name: "with the option an unmatched route returns 404",
opts: DenyDefaultMuxFallthrough(),
path: "/debug/sentinel",
wantStatus: nhttp.StatusNotFound,
},
{
name: "with the option a registered route still works",
opts: DenyDefaultMuxFallthrough(),
path: "/healthz",
wantStatus: nhttp.StatusOK,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
srv := http.NewServer(tc.opts...)
srv.HandleFunc("/healthz", func(w nhttp.ResponseWriter, _ *nhttp.Request) {
w.WriteHeader(nhttp.StatusOK)
})

rec := httptest.NewRecorder()
req := httptest.NewRequest(nhttp.MethodGet, tc.path, nil)
srv.ServeHTTP(rec, req)

assert.Equal(t, tc.wantStatus, rec.Code)
if tc.path == "/debug/sentinel" && tc.wantStatus == nhttp.StatusNotFound {
assert.NotContains(t, rec.Body.String(), "LEAKED")
}
})
}
}
Loading