oauthex: oauth discovery checks (#1220)

add extra checks for the default oauth discovery client:
* disallow redirects which downgrade to http
* disallow redirects to localhost or private ip
* disallow the initial call to a private ip
* proxy configuration opts out of dial time checking
This commit is contained in:
Yaroslav 2026-09-02 21:07:19 +02:00 • committed by GitHub
commit 12cbafe54d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,7 +3,10 @@
// that can be found in the LICENSE file.
package util
import "testing"
import (
"net/netip"
"testing"
)
// TestIsLoopback tests the IsLoopback helper function.
func TestIsLoopback(t *testing.T) {
@ -33,3 +36,52 @@ func TestIsLoopback(t *testing.T) {
})
}
}
func TestIsPrivateOrReserved(t *testing.T) {
tests := []struct {
ip string
want bool
}{
// Private (RFC 1918) and IPv6 ULA (RFC 4193).
{"10.0.0.1", true},
{"172.16.5.4", true},
{"192.168.1.1", true},
{"fd00::1", true},
// Link-local, including the cloud metadata endpoint.
{"169.254.169.254", true},
{"169.254.0.1", true},
{"fe80::1", true},
// Carrier-grade NAT (RFC 6598).
{"100.64.0.1", true},
{"100.127.255.255", true},
// Multicast and unspecified.
{"224.0.0.1", true},
{"ff02::1", true},
{"0.0.0.0", true},
{"::", true},
// IPv4-mapped IPv6 of a private address must also be caught.
{"::ffff:10.0.0.1", true},
{"::ffff:192.168.0.1", true},
// Loopback is intentionally NOT reserved here (policy handled elsewhere).
{"127.0.0.1", false},
{"::1", false},
// Public addresses are allowed.
{"8.8.8.8", false},
{"1.1.1.1", false},
{"2606:4700:4700::1111", false},
// Just outside CGNAT.
{"100.63.255.255", false},
{"100.128.0.0", false},
}
for _, tt := range tests {
t.Run(tt.ip, func(t *testing.T) {
ip, err := netip.ParseAddr(tt.ip)
if err != nil {
t.Fatalf("ParseAddr(%q): %v", tt.ip, err)
}
if got := IsPrivateOrReserved(ip); got != tt.want {
t.Errorf("IsPrivateOrReserved(%q) = %v, want %v", tt.ip, got, tt.want)
}
})
}
}