@@ -80,6 +80,11 @@ func (s *GatewayService) CreateRoute(ctx context.Context, params ports.CreateRou
8080 paramNames = matcher .ParamNames
8181 }
8282
83+ // Validate target URL to prevent SSRF attacks
84+ if err := isAllowedTarget (params .Target ); err != nil {
85+ return nil , fmt .Errorf ("invalid target: %w" , err )
86+ }
87+
8388 route := & domain.GatewayRoute {
8489 ID : uuid .New (),
8590 UserID : userID ,
@@ -620,3 +625,52 @@ func (rt *retryTransport) jitter(max time.Duration) time.Duration {
620625 frac := val / float64 (math .MaxUint64 )
621626 return time .Duration (float64 (max ) * frac )
622627}
628+
629+ // isAllowedTarget validates that a target URL doesn't point to internal/private networks.
630+ // This prevents SSRF attacks where an attacker could route requests to cloud metadata
631+ // endpoints (169.254.169.254), localhost, or private IP ranges.
632+ func isAllowedTarget (targetURL string ) error {
633+ u , err := url .Parse (targetURL )
634+ if err != nil {
635+ return fmt .Errorf ("invalid target URL: %w" , err )
636+ }
637+
638+ host := u .Hostname ()
639+ ip := net .ParseIP (host )
640+
641+ // Check for localhost
642+ if host == "localhost" || host == "127.0.0.1" {
643+ return fmt .Errorf ("localhost targets not allowed" )
644+ }
645+
646+ // Check for loopback IP
647+ if ip != nil && ip .IsLoopback () {
648+ return fmt .Errorf ("loopback targets not allowed" )
649+ }
650+
651+ // Check for link-local (169.254.x.x - Azure/AWS metadata)
652+ if ip != nil && ip .IsLinkLocalUnicast () {
653+ return fmt .Errorf ("link-local addresses not allowed" )
654+ }
655+
656+ // Check for private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
657+ if ip != nil && ip .IsPrivate () {
658+ return fmt .Errorf ("private IP targets not allowed" )
659+ }
660+
661+ // Check for reserved metadata addresses (169.254.0.0/16)
662+ if ip != nil && isReservedIP (ip ) {
663+ return fmt .Errorf ("reserved IP targets not allowed" )
664+ }
665+
666+ return nil
667+ }
668+
669+ // isReservedIP checks for IP addresses used by cloud metadata services.
670+ func isReservedIP (ip net.IP ) bool {
671+ // 169.254.0.0/16 - Azure/AWS/gcp metadata endpoints
672+ if len (ip ) >= 2 && ip [0 ] == 169 && ip [1 ] == 254 {
673+ return true
674+ }
675+ return false
676+ }
0 commit comments