fix(server): apply the outbound address policy to remote cert pinning

The remote certificate fetch now dials through the same netsafe guard
as the REALITY target scan. A private or loopback endpoint is refused
unless the request carries allowPrivate; the inbound form asks the
operator to confirm and retries with the opt-in.
This commit is contained in:
MHSanaei
2026-10-03 13:20:00 +02:00
parent d31465e37b
commit ede275e4dc
8 changed files with 87 additions and 8 deletions
@@ -0,0 +1,38 @@
package service
import (
"crypto/sha256"
"encoding/hex"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
)
func TestGetRemoteCertHashGuardsPrivateTargets(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
defer srv.Close()
target := strings.TrimPrefix(srv.URL, "https://")
sum := sha256.Sum256(srv.Certificate().Raw)
want := hex.EncodeToString(sum[:])
t.Run("loopback refused without opt-in", func(t *testing.T) {
hashes, err := (&ServerService{}).GetRemoteCertHash(target, false)
if !errors.Is(err, netsafe.ErrPrivateAddressBlocked) {
t.Fatalf("GetRemoteCertHash(%s) = %v, %v; want ErrPrivateAddressBlocked", target, hashes, err)
}
})
t.Run("loopback read with opt-in", func(t *testing.T) {
hashes, err := (&ServerService{}).GetRemoteCertHash(target, true)
if err != nil {
t.Fatalf("GetRemoteCertHash(%s, allowPrivate): %v", target, err)
}
if len(hashes) != 1 || hashes[0] != want {
t.Fatalf("hashes = %v, want [%s]", hashes, want)
}
})
}