Files
vm/api/server/allowed_hosts.go
zeekayandHanzo Dev e9e9fb6315 Give the node's HTTP server a home beside the VMs it serves
api/server was an earlier copy of node/server/http that had drifted behind it.
Its exported surface was a strict subset of the node's, so this replaces the
contents rather than adding a second package: a second server would have meant
two Server interfaces, two New, two routers, and no answer to which one a chain
should mount on.

What arrives with it is the part api/server never had. Mount is the estate's
only projection from a zip App to an http.Handler, and it installs Authorize
before it serves, so an app cannot reach the router without the node's
authorization decision on it. Chain builds the one address a chain answers at,
and chain_test walks this module's own source to hold every caller to it. The
ZAP-RPC listener and the /v1 prefix come across as well; api/server was still
serving /ext.

Two files stay behind because they already have homes here. The node's
traced_handler duplicates api.TraceHandler, which this package was already
calling, so server.go keeps calling it and takes luxfi/trace in place of the
node-local trace shim.

Two tests do not come across, and neither is dropped from the node. mount_test
builds a real Z-Chain from luxfi/chains, which requires luxfi/vm, so importing
it here would point the layering back at itself. probe_test needs the Ops app
on node/service/health, which api/health does not carry. Both keep running
where they are.

constants moves to v1.6.4 to agree with the node on the chain segment. The two
modules disagreed, v1.6.2 still saying bc where the node says chain, and the
address Chain builds has to be the address the node's router serves.

newMetrics now reports a duplicate registration instead of returning a
half-built set, which is what TestMetricsRegistrationFailure had been asking
for and failing to get. That test stays, next to the node's, because the two
ask different questions: one registry twice, or two registries once.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 12:21:19 -07:00

89 lines
2.1 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package server
import (
"net"
"net/http"
"strings"
"github.com/go-json-experiment/json"
"github.com/luxfi/math/set"
)
const wildcard = "*"
var _ http.Handler = (*allowedHostsHandler)(nil)
func filterInvalidHosts(
handler http.Handler,
allowed []string,
) http.Handler {
s := make(set.Set[string])
for _, host := range allowed {
if host == wildcard {
// wildcards match all hostnames, so just return the base handler
return handler
}
s.Add(strings.ToLower(host))
}
return &allowedHostsHandler{
handler: handler,
hosts: s,
}
}
// allowedHostsHandler is an implementation of http.Handler that validates the
// http host header of incoming requests. This can prevent DNS rebinding attacks
// which do not utilize CORS-headers. Http request host headers are validated
// against a whitelist to determine whether the request should be dropped or
// not.
type allowedHostsHandler struct {
handler http.Handler
hosts set.Set[string]
}
func (a *allowedHostsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// if the host header is missing we can serve this request because dns
// rebinding attacks rely on this header
if r.Host == "" {
a.handler.ServeHTTP(w, r)
return
}
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
// either invalid (too many colons) or no port specified
host = r.Host
}
if ipAddr := net.ParseIP(host); ipAddr != nil {
// accept requests from ips
a.handler.ServeHTTP(w, r)
return
}
// a specific hostname - we need to check the whitelist to see if we should
// accept this r
if a.hosts.Contains(strings.ToLower(host)) {
a.handler.ServeHTTP(w, r)
return
}
// Return error as JSON
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_ = json.MarshalWrite(w, map[string]interface{}{
"jsonrpc": "2.0",
"error": map[string]interface{}{
"code": -32001,
"message": "invalid host specified",
"data": map[string]string{"host": host},
},
"id": nil,
})
}