+
    Q(i*                    f   R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RI	H
t
 ^ RIHt ^ RIt^ RIHt ]! ]4      tR R lt ! R	 R
]4      t ! R R]4      tR R ltR%R R llt]
 ! R R4      4       t]
 ! R R4      4       tR&R R lltRRRRRRRR/R R  lltRRRRRRRRR!RR"R/R# R$ lltR# )'zSSRF-safe HTTP utilities for FastMCP.

This module provides SSRF-protected HTTP fetching with:
- DNS resolution and IP validation before requests
- DNS pinning to prevent rebinding TOCTOU attacks
- Support for both CIMD and JWKS fetches
)annotationsN)Mapping)	dataclass)urlparse)
get_loggerc                    V ^8  d   QhRRRR/# )   ip_strstrreturn )formats   "^/Users/agent/.openclaw/workspace/venv/lib/python3.14/site-packages/fastmcp/server/auth/ssrf.py__annotate__r      s      c c     c                     \         P                  ! V 4      p\        V\         P                  4      '       d   RV  R2# V #   \         d    T u # i ; i)a2  Format IP address for use in URL (bracket IPv6 addresses).

IPv6 addresses must be bracketed in URLs to distinguish the address from
the port separator. For example: https://[2001:db8::1]:443/path

Args:
    ip_str: IP address string

Returns:
    IP string suitable for URL (IPv6 addresses are bracketed)
[])	ipaddress
ip_address
isinstanceIPv6Address
ValueError)r	   ips   & r   format_ip_for_urlr      sQ    !!&)b)//00vha=  s   ;A  A   AAc                      ] tR t^/tRtRtR# )	SSRFErrorz+Raised when an SSRF protection check fails.r   N__name__
__module____qualname____firstlineno____doc____static_attributes__r   r   r   r   r   /   s    5r   r   c                      ] tR t^3tRtRtR# )SSRFFetchErrorz"Raised when SSRF-safe fetch fails.r   Nr   r   r   r   r%   r%   3   s    ,r   r%   c                    V ^8  d   QhRRRR/# )r   r	   r
   r   boolr   )r   s   "r   r   r   7   s     ( (# ($ (r   c                H    \         P                  ! V 4      pTP                  '       g   R# TP                  '       d   R# \        T\         P                  4      '       d   TP                  '       d   \        \        TP                  4      4      # TP                  '       d   \        \        TP                  4      4      # TP                  '       d?   TP                  w  r#\        \        T4      4      ;'       d    \        \        T4      4      # R#   \         d     R# i ; i)a  Check if an IP address is allowed (must be globally routable unicast).

Uses ip.is_global which catches:
- Private (10.x, 172.16-31.x, 192.168.x)
- Loopback (127.x, ::1)
- Link-local (169.254.x, fe80::) - includes AWS metadata!
- Reserved, unspecified
- RFC6598 Carrier-Grade NAT (100.64.0.0/10) - can point to internal networks

Additionally blocks multicast addresses (not caught by is_global).

Args:
    ip_str: IP address string to check

Returns:
    True if the IP is allowed (public unicast internet), False if blocked
FT)r   r   r   	is_globalis_multicastr   r   ipv4_mappedis_ip_allowedr
   	sixtofourteredo)r	   r   serverclients   &   r   r,   r,   7   s    $!!&) <<< 
 "i++,,>>> R^^!455<<< R\\!233999YYNF V-LL-F2LL)  s   D D! D!c               $    V ^8  d   QhRRRRRR/# )r   hostnamer
   portintr   	list[str]r   )r   s   "r   r   r   b   s&     M MS M Mi Mr   c                d  a a"   \         P                  ! 4       p VP                  RV V3R l4      G Rj  xL
 p\        V Uu0 uF  qD^,          ^ ,          kK  	  up4      pV'       g   \	        RS  24      hV#  LDu upi   \
        P                   d   p\	        RS  RT 24      ThRp?ii ; i5i)zResolve hostname to IP addresses using DNS.

Args:
    hostname: Hostname to resolve
    port: Port number (used for getaddrinfo)

Returns:
    List of resolved IP addresses

Raises:
    SSRFError: If resolution fails
Nc                 n   < \         P                  ! S S\         P                  \         P                  4      # )N)socketgetaddrinfo	AF_UNSPECSOCK_STREAM)r2   r3   s   r   <lambda>"resolve_hostname.<locals>.<lambda>s   s%    F&&$ 0 0&2D2Dr   z)DNS resolution returned no addresses for zDNS resolution failed for : )asyncioget_running_looprun_in_executorlistr   r8   gaierror)r2   r3   loopinfosinfoipses   ff     r   resolve_hostnamerI   b   s      ##%DM**
 
 5154GAJJ512GzRSS

 2 ?? M4XJbDE1LMsP   B0B A:B A<B *B 9B0:B <B B-B((B--B0c                  N    ] tR t^t$ RtR]R&   R]R&   R]R&   R]R&   R]R	&   R
tR# )ValidatedURLz9A URL that has been validated for SSRF with resolved IPs.r
   original_urlr2   r4   r3   pathr5   resolved_ipsr   Nr   r   r    r!   r"   __annotations__r#   r   r   r   rK   rK      s     CM
I
Ir   rK   c                  :    ] tR t^t$ RtR]R&   R]R&   R]R&   RtR	# )
SSRFFetchResponsez)Response payload from an SSRF-safe fetch.bytescontentr4   status_codezdict[str, str]headersr   NrO   r   r   r   rR   rR      s    3Nr   rR   Fc               $    V ^8  d   QhRRRRRR/# )r   urlr
   require_pathr'   r   rK   r   )r   s   "r   r   r      s!     . .C .t . .r   c                  "    \        V 4      pTP                  R8w  d   \        RTP                   24      hTP
                  '       g   \        R4      hT'       d   TP                  R9   d   \        R4      hTP                  ;'       g    TP
                  pTP                  ;'       g    Rp\        YE4      G Rj  xL
 pT Uu. uF  p\        T4      '       d   K  TNK  	  ppT'       d   \        R	T R
24      h\        T TTTP                  TP                  '       d   RTP                  ,           MR,           TR7      #   \        \        3 d   p\        RT 24      ThRp?ii ; i Lu upi 5i)zValidate URL for SSRF and resolve to IPs.

Args:
    url: URL to validate
    require_path: If True, require non-root path (for CIMD)

Returns:
    ValidatedURL with resolved IPs

Raises:
    SSRFError: If URL is invalid or resolves to blocked IPs
zInvalid URL: NhttpszURL must use HTTPS, got: zURL must have a host zURL must have a non-root path  z(URL resolves to blocked IP address(es): zB. Private, loopback, link-local, and reserved IPs are not allowed.?)rL   r2   r3   rM   rN   )r\   /)r   r   AttributeErrorr   schemenetlocrM   r2   r3   rI   r,   rK   query)	rX   rY   parsedrH   r2   r3   rN   r   blockeds	   &&       r   validate_urlrf      sJ    4# }}3FMM?CDD===.//y0788//&--H;;#D *(99L(BLbb0ArrLGB6wi @O P
 	

 [[&,,,C&,,.BG! 3 ' 4-s+,!34  :Bsd   E2E :E2
E2.E2E2,E2=E+>E2E-E-$AE2E(E##E((E2-E2rY   max_sizei   timeoutg      $@overall_timeoutg      >@c               0    V ^8  d   QhRRRRRRRRR	RR
R/# )r   rX   r
   rY   r'   rg   r4   rh   floatri   r   rS   r   )r   s   "r   r   r      sF     ( (	( ( 	(
 ( ( (r   c          	     Z   "   \        V VVVV^0R7      G Rj  xL
 pVP                  #  L5i)au  Fetch URL with comprehensive SSRF protection and DNS pinning.

Security measures:
1. HTTPS only
2. DNS resolution with IP validation
3. Connects to validated IP directly (DNS pinning prevents rebinding)
4. Response size limit
5. Redirects disabled
6. Overall timeout

Args:
    url: URL to fetch
    require_path: If True, require non-root path
    max_size: Maximum response size in bytes (default 5KB)
    timeout: Per-operation timeout in seconds
    overall_timeout: Overall timeout for entire operation

Returns:
    Response body as bytes

Raises:
    SSRFError: If SSRF validation fails
    SSRFFetchError: If fetch fails
)rY   rg   rh   ri   allowed_status_codesN)ssrf_safe_fetch_responserT   )rX   rY   rg   rh   ri   responses   &$$$$ r   ssrf_safe_fetchrp      s>     @ .!'!U H s   +)+request_headersrm   c               8    V ^8  d   QhRRRRRRRRR	RR
RRRRR/# )r   rX   r
   rY   r'   rg   r4   rh   rk   ri   rq   zMapping[str, str] | Nonerm   zset[int] | Noner   rR   r   )r   s   "r   r   r      sn     uM uM	uM uM 	uM
 uM uM .uM *uM uMr   c                 "   \         P                  ! 4       p\        WR7      G Rj  xL
 pRp	T;'       g    ^0p
VP                   EF  p\         P                  ! 4       V,
          pW8  d   \	        RV  24      h\        RWL,
          4      pR\        V4       RVP                   VP                   2p\        P                  RV VV4       RVP                  /pV'       d7   VP                  4        F"  w  ppVP                  4       R	8X  d   K  VVV&   K$  	   \        P                  ! \        P                   ! \#        W=4      \#        W=4      \#        W=4      \#        W=4      R
7      RRR7      ;_uu_4       GRj  xL
 pVP%                  RVVRVP                  /R7      ;_uu_4       GRj  xL
 p\         P                  ! 4       V,
          V8  d   \	        RV  24      hVP&                  V
9  d   \	        RVP&                   RV  24      hVP(                  P+                  R4      pV'       d'    \-        V4      pVV8  d   \	        RV RV R24      h . p^ pVP1                  4         Rj  xL
  p\         P                  ! 4       V,
          V8  d   \	        RV  24      hV\3        V4      ,          pVV8  d   \	        RV R24      hVP5                  V4       Kt  	  V	eB   \A        V	\        P<                  4      '       d   \	        RV  24      V	h\	        RV  RV	 24      V	h\	        RV  R24      h EL EL EL  \.         d     Li ; i LD\7        RP9                  T4      TP&                  \;        TP(                  4      R7      uuRRR4      GRj  xL 
  uuRRR4      GRj  xL 
  u #   + GRj  xL 
 '       g   i     M; iRRR4      GRj  xL 
  EK    + GRj  xL 
 '       g   i     EK  ; i  \        P<                   d   pTp	 Rp?EK  Rp?i\        P>                   d   pTp	 Rp?EK  Rp?ii ; i5i)zFetch URL with SSRF protection and return response metadata.

This is equivalent to :func:`ssrf_safe_fetch` but returns response headers
and status code, and supports conditional request headers.
)rY   NzOverall timeout exceeded: g      ?zhttps://:z(SSRF-safe fetch: %s -> %s (pinned to %s)Hosthost)connectreadwritepoolFT)rh   follow_redirectsverifyGETsni_hostname)rV   
extensionszHTTP z
 fetching zcontent-lengthzResponse too large: z bytes (max )zResponse too large: exceeded z bytesr   )rT   rU   rV   zTimeout fetching zError fetching r>   z: no resolved IPs succeeded)!time	monotonicrf   rN   r%   maxr   r3   rM   loggerdebugr2   itemslowerhttpxAsyncClientTimeoutminstreamrU   rV   getr4   r   aiter_byteslenappendrR   joindictTimeoutExceptionRequestErrorr   )rX   rY   rg   rh   ri   rq   rm   
start_time	validated
last_errorexpected_statuses	pinned_ipelapsed	remaining
pinned_urlrV   keyvaluer0   ro   content_lengthsizechunkstotalchunkrH   s   &$$$$$$                   r   rn   rn      s     !J #3BBI#'J,55++	.."Z/$ #=cU!CDD_67	 (34Ainn5EinnEUV 	 	6		
 9--.-335
U99;&($	 6>	 !!!MM #G 7 4!'5 4	 &+	 	 	 # .	0B0BC	    
 >>#j0?B(+EcU)KLL''/@@(51E1E0FjQTPU)VWW "*!1!1!5!56F!G!">2(?"0"6tfL
RS T#  + #+#7#7#9 ) )%~~'*4F,/I#-OPPSZ'Ex',;H:VL  MM%(U ,r j%"8"899 #4SE!:;Kse2j\BCS
?3%/JK
LLG CB	, & )#9 )HHV, ( 4 4 !1!12M  	 	 	   	 	 	 	 	 	l %% 	J!! 	J	s]  %QL3QC!QA!P :L6
;P >.O",L9-O"0A>N20$L<
N2)M-M.M1A,N2AQ6P 9O"<M
N2	M

N2M:N2	O"NO"P )N,*P /Q2O8N;9
OOO"P OP Q"O=(O+)
O=4O=6P :Q=P  QPQQ5Q6P?8Q?QQ)r]   )F)r"   
__future__r   r?   r   r8   r   collections.abcr   dataclassesr   urllib.parser   r   fastmcp.utilities.loggingr   r   r   r   	Exceptionr   r%   r,   rI   rK   rR   rf   rp   rn   r   r   r   <module>r      s   #     # ! !  0	H	*6	 6-Y -(VM:      .b( ( 	(
 ( "(VuM uM 	uM
 uM "uM 15uM -1uM uMr   