
    .cj}                        U d Z ddlZddlZddlZddlZddlZddlZddlmZm	Z	 ddl
mZmZmZmZmZmZmZ ddlmZ  ej        e          ZdedefdZ eh d	          Zdede	e         fd
ZdedefdZ eddh          Z e ej        d           ej        d           ej        d           ej        d           ej        d           ej        d           ej        d           ej        d           ej        d          h	          Z  ej!        d           ej!        d          fZ" edh          Z#dZ$ ej!        d          Z%da&da'ee(d<   defdZ)dCdZ*d ej+        ej,        z  defd!Z-dedefd"Z.d#ed$edefd%Z/dedefd&Z0dedefd'Z1 G d( d)e2          Z3d*ed+e4d,e5e6ee4f         ef         defd-Z7d*ed+e4d$ede8e         fd.Z9 G d/ d0          Z: G d1 d2          Z;d3ede5e6ee4f         ef         fd4Z<d5edefd6Z=d5edefd7Z>d8ed9eddfd:Z?d8ed9eddfd;Z@d<eddfd=ZAd<eddfd>ZBd5edefd?ZCd5edefd@ZDdAede	e         fdBZEdS )Du  URL safety checks — blocks requests to private/internal network addresses.

Prevents SSRF (Server-Side Request Forgery) where a malicious prompt or
skill could trick the agent into fetching internal resources like cloud
metadata endpoints (169.254.169.254), localhost services, or private
network hosts.

The check can be globally disabled via ``security.allow_private_urls: true``
in config.yaml for environments where DNS resolves external domains to
private/benchmark-range IPs (OpenWrt routers, corporate proxies, VPNs
that use 198.18.0.0/15 or 100.64.0.0/10).  Even when disabled, cloud
metadata hostnames (metadata.google.internal, 169.254.169.254) are
**always** blocked — those are never legitimate agent targets.

Limitations:
  - DNS rebinding (TOCTOU): an attacker-controlled DNS server with TTL=0
    can return a public IP for the check, then a private IP for the actual
    connection. Hermes-owned direct httpx request paths should use
    ``create_ssrf_safe_client()`` / ``create_ssrf_safe_async_client()`` so the
    same policy is applied immediately before TCP connect and the client
    connects to the validated IP while preserving Host/SNI semantics.
  - Redirect-based bypass is mitigated by httpx event hooks that re-validate
    each redirect target in vision_tools, gateway platform adapters, and
    media cache helpers. Web tools use third-party SDKs (Firecrawl/Tavily)
    where redirect handling is on their servers.
    N)AnyOptional)	parse_qslquoteunquoteurljoinurlparseurlsplit
urlunsplit)is_truthy_valueurlreturnc                    t          | t                    s| S |                                 }|s|S t          j        dd|          }	 t          |          }n# t          $ r |cY S w xY w|j                                        dvr|S |j	        }|j
        }|rY	 |                    d                              d          }n# t          $ r |}Y nw xY w||k    r|                    ||d          }t          |j        d          }t          |j        d	          }t          |j        d	          }t'          |j        ||||f          S )
u  Return an ASCII-safe HTTP URL for Hermes-owned URL tools.

    Browsers and HTTP clients expect URIs, but users and models often provide
    IRIs such as ``https://wttr.in/Köln``.  Preserve URL syntax and existing
    percent escapes while encoding non-ASCII host/path/query/fragment text.
    This is intentionally for URL tool inputs only; arbitrary shell commands
    must not be rewritten.
    z ^([A-Za-z][A-Za-z0-9+.-]*://)\s+z\1>   httphttpsidnaascii   z/%:@!$&'()*+,;=)safez/%:@!$&'()*+,;=?)
isinstancestrstripresubr
   
ValueErrorschemelowernetlochostnameencodedecodeUnicodeErrorreplacer   pathqueryfragmentr   )	r   rawparsedr   r   
ascii_hostr$   r%   r&   s	            2/home/ice/.hermes/hermes-agent/tools/url_safety.pynormalize_url_for_requestr+   *   s|    c3 

))++C 
 &4eS
A
AC#   


 }$555
]FH =	"!0077@@JJ 	" 	" 	"!JJJ	"!!^^Hj!<<F#4555D&,%7888EV_+=>>>Hv}fdE8DEEEs$   A A&%A&(C   CC>   x-amz-signaturex-amz-security-tokenjwttokenapikeypasswdsecretapi_keypassword	signature
auth_token
credential
session_idcredentialsaccess_tokenauthorizationclient_secretawsaccesskeyidx_amz_signaturex_amz_security_tokenc                    t          | t                    rd| vrdS 	 t          |                                           }n# t          $ r Y dS w xY w|j                                        dvs|j        sdS t          |j        d          D ]3\  }}|r,t          |                                          t          v r|c S 4dS )al  Return the first sensitive query parameter name in ``url``, if any.

    Used before handing URLs to third-party fetch/browser backends. Prefix-based
    token redaction catches known credential shapes; this catches opaque magic
    links, OAuth codes, signed URL signatures, and custom ``?token=...`` values
    that do not have a recognizable vendor prefix.
    ?N>   r   r   T)keep_blank_values)r   r   r
   r   r   r   r   r%   r   r   _SENSITIVE_QUERY_PARAM_NAMES)r   r(   keyvalues       r*   sensitive_query_param_namerF   y   s     c3 3c>>t#))++&&   tt}$555V\5tEEE  
U 	WS\\''))-IIIJJJ4s   !? 
AAc                 $    t          |           duS )zHReturn True when ``url`` carries likely credential-bearing query params.N)rF   r   s    r*   has_sensitive_query_paramsrI      s    %c**$66    zmetadata.google.internalzmetadata.googz169.254.169.254z169.254.170.2z169.254.169.253zfd00:ec2::254z100.100.100.200z::ffff:169.254.169.254z::ffff:169.254.170.2z::ffff:169.254.169.253z::ffff:100.100.100.200z169.254.0.0/16z::ffff:169.254.0.0/112zmultimedia.nt.qq.com.cn   z100.64.0.0/10F_cached_allow_privatec                  z   t           rt          S da dat          j        dd                                                                          } | dv r	dat          S | dv rt          S 	 ddlm}  |            }|                    d	i           }t          |t                    r-t          |                    d
          d          r	dat          S |                    di           }t          |t                    r-t          |                    d
          d          r	dat          S n# t          $ r Y nw xY wt          S )ac  Return True when the user has opted out of private-IP blocking.

    Checks (in priority order):
    1. ``HERMES_ALLOW_PRIVATE_URLS`` env var  (``true``/``1``/``yes``)
    2. ``security.allow_private_urls`` in config.yaml
    3. ``browser.allow_private_urls`` in config.yaml  (legacy / backward compat)

    Result is cached for the process lifetime.
    TFHERMES_ALLOW_PRIVATE_URLS >   1yestrue>   0nofalser   )read_raw_configsecurityallow_private_urls)defaultbrowser)_allow_private_resolvedrL   osgetenvr   r   hermes_cli.configrV   getr   dictr   	Exception)env_valrV   cfgsecrZ   s        r*   _global_allow_private_urlsre      sw     %$$"! i3R88>>@@FFHHG&&& $$$&&&$$555555oggj"%%c4   	)_GG())5&
 &
 &
 	) %)!(('')R((gt$$ 	)KK,--u*
 *
 *
 	) %)!((    ! s   %A'D& AD& &
D32D3c                      da dadS )u+   Reset the cached toggle — only for tests.FN)r[   rL    rJ   r*   _reset_allow_private_cacherh      s     $!rJ   ipc                 .   t          | t          j                  rA| j        :| j        }|j        p+|j        p$|j        p|j        p|j        p|j	        p|t          v S | j        s| j        s| j        s| j        rdS | j        s| j	        rdS | t          v rdS dS )z<Return True if the IP should be blocked for SSRF protection.NTF)r   	ipaddressIPv6Addressipv4_mapped
is_privateis_loopbackis_link_localis_reservedis_multicastis_unspecified_CGNAT_NETWORK)ri   embedded_ips     r*   _is_blocked_iprv     s     "i+,, /1Kn& .+*A .).-8-D.(.,7,F. ~-	/ 
}  "*: bn t	 "+ t	^t5rJ   c                 T  	
 	 t          |           }|j        pd                                                                                    d          }|sdS |t
          v rt                              d|           dS 	 t          j	        |          	n# t          $ r d	Y nw xY w	H	t          v s t          	fdt          D                       rt                              d|           dS dS 	 t          j        |dt          j        t          j                  }n# t          j        $ r Y dS w xY w|D ]\  }}}}}|d	         }d
|v r|                    d
          d	         }	 t          j	        |          
n2# t          $ r% t                              d|d	         |           Y sw xY w
t          v s t          
fdt          D                       rt                              d||            dS dS # t(          $ r'}t                              d| |           Y d}~dS d}~ww xY w)u  Return True when the URL targets an always-blocked endpoint.

    This is the security floor — cloud metadata IPs / hostnames
    (169.254.169.254, metadata.google.internal, ECS task metadata, etc.)
    that have no legitimate agent use regardless of backend, routing, or
    the ``allow_private_urls`` toggle.  Used by callers that bypass the
    full ``is_safe_url`` check for their own reasons (e.g. hybrid cloud
    browser routing to a local Chromium sidecar for private URLs) and
    still need to enforce the non-negotiable floor before letting the
    request proceed.

    Returns True (= blocked) on:
      - Hostnames in ``_BLOCKED_HOSTNAMES``
      - IPs / networks in ``_ALWAYS_BLOCKED_IPS`` / ``_ALWAYS_BLOCKED_NETWORKS``
      - URLs whose hostname resolves to any of the above

    Returns False (= not in the always-blocked floor) on:
      - Benign public / private / loopback URLs (whether or not they'd
        be blocked by the ordinary SSRF check)
      - DNS-resolution failures for non-sentinel hostnames (these are
        someone else's problem — the caller's ordinary fail-closed path
        will catch them if applicable)
      - Parse errors (caller decides fail-open vs fail-closed)

    Intentionally narrower than ``is_safe_url``: only blocks the sentinel
    set, not ordinary private addresses.  Callers that want the full
    SSRF check should still use ``is_safe_url``.
    rO   .Fz?Blocked request to internal hostname (always-blocked floor): %sTNc              3       K   | ]}|v V  	d S Nrg   .0netri   s     r*   	<genexpr>z(is_always_blocked_url.<locals>.<genexpr>N  s8       0 0!c	0 0 0 0 0 0rJ   zDBlocked request to cloud metadata address (always-blocked floor): %sr   %u>   Unparseable IP address %r for hostname %s — skipping addressc              3       K   | ]}|v V  	d S rz   rg   )r|   r}   resolveds     r*   r~   z(is_always_blocked_url.<locals>.<genexpr>k  s8       6 6$'C6 6 6 6 6 6rJ   zJBlocked request to cloud metadata address (always-blocked floor): %s -> %sz&is_always_blocked_url error for %s: %s)r	   r   r   r   rstrip_BLOCKED_HOSTNAMESloggerwarningrk   
ip_addressr   _ALWAYS_BLOCKED_IPSany_ALWAYS_BLOCKED_NETWORKSsocketgetaddrinfo	AF_UNSPECSOCK_STREAMgaierrorsplitra   debug)r   r(   r   	addr_info_family_sockaddrip_strexcri   r   s            @@r*   is_always_blocked_urlr     s   :C#O)r002288::AA#FF 	5 )))NNQ   4	%h//BB 	 	 	BBB	 >(((C 0 0 0 0%=0 0 0 - -( 1  
 t5	*$ 0&2D II  	 	 	55	 +4 	 	&GQ1ha[Ff}}c**1-$/77   _aijkalnvwww ...# 6 6 6 6+C6 6 6 3 3. 7	   tt / u    	=sCHHHuuuuu	s   AG6 $G6 >B G6 B"G6 !B""A	G6 0+D G6 D/+G6 .D//4G6 $E98G6 9,F(%G6 'F((A	G6 3G6 6
H' H""H'r   r   c                      |dk    o| t           v S )zGReturn True when a trusted HTTPS hostname may bypass IP-class blocking.r   )_TRUSTED_PRIVATE_IP_HOSTS)r   r   s     r*   _allows_private_ip_resolutionr     s    WF-F!FFrJ   c                 ^   	 t          |           }|j        pd                                                                                    d          }|j        pd                                                                }|dvrt                              d|pd           dS |sdS |t          v rt                              d|           dS t                      }t          ||          }	 t          j        |dt          j        t          j                  }n1# t          j        $ r t                              d	|           Y dS w xY w|D ]\  }}}}}	|	d
         }
d|
v r|
                    d          d
         }
	 t#          j        |
          n4# t&          $ r' t                              d|	d
         |           Y  dS w xY wt(          v s t+          fdt,          D                       rt                              d||
            dS |s0|s.t/                    rt                              d||
            dS |rt                              d|           n|rt                              d|           dS # t2          $ r'}t                              d| |           Y d}~dS d}~ww xY w)u  Return True if the URL target is not a private/internal address.

    Resolves the hostname to an IP and checks against private ranges.
    Fails closed: DNS errors and unexpected exceptions block the request.

    When ``security.allow_private_urls`` is enabled (or the env var
    ``HERMES_ALLOW_PRIVATE_URLS=true``), private-IP blocking is skipped.
    Cloud metadata endpoints (169.254.169.254, metadata.google.internal)
    remain blocked regardless — they are never legitimate agent targets.
    rO   rx   >   r   r   u.   Blocked request — unsupported URL scheme: %sz<empty>Fz(Blocked request to internal hostname: %sNu1   Blocked request — DNS resolution failed for: %sr   r   u=   Blocked request — unparseable IP address %r for hostname %sc              3       K   | ]}|v V  	d S rz   rg   r{   s     r*   r~   zis_safe_url.<locals>.<genexpr>  s'      /^/^cc	/^/^/^/^/^/^rJ   z3Blocked request to cloud metadata address: %s -> %sz5Blocked request to private/internal address: %s -> %szKAllowing private/internal resolution (security.allow_private_urls=true): %szAAllowing trusted hostname despite private/internal resolution: %sTu5   Blocked request — URL safety check error for %s: %s)r	   r   r   r   r   r   r   r   r   re   r   r   r   r   r   r   r   rk   r   r   r   r   r   rv   r   ra   )r   r(   r   r   allow_all_privateallow_private_ipr   familyr   r   r   r   ri   s               @r*   is_safe_urlr     s#   H#O)r002288::AA#FF-%2,,..4466***NNKVM`W`aaa5 	5 )))NNExPPP5 78886JJ	*8T6;KVM_``II 	 	 	 NNNPXYYY55		 *3 	 	%FAq!Xa[Ff}}c**1-)&11   ^`hij`kmuvvvuuu (((C/^/^/^/^E]/^/^/^,^,^(If   uu$ -= .QSBTBT Kf   uu 		LL]     	LLS  
 t    	NPSUXYYYuuuuu	s   BI; "I; &$I; I; ++D I; *EI; E4I; :FI; ,G ;I; ?G  A	I; 0I; =<I; ;
J,J''J,c                 F   K   t          j        t          |            d{V S )zSame rules as :func:`is_safe_url`, but run the DNS work off the event loop.

    ``socket.getaddrinfo`` can block; call this from async code paths (gateway,
    ``web_extract_tool``, vision download hooks) instead of ``is_safe_url``.
    N)asyncio	to_threadr   rH   s    r*   async_is_safe_urlr     s-       ";444444444rJ   c                       e Zd ZdZdS )SSRFConnectionBlockedzGRaised when connect-time DNS resolution violates the URL safety policy.N)__name__
__module____qualname____doc__rg   rJ   r*   r   r     s        QQQQrJ   r   hostportschemes_by_originc                 D    |                     | |f          p	|dk    rdndS )Ni  r   r   )r_   )r   r   r   s      r*   _safe_connect_schemer     s+      $..Vdckk77vVrJ   c                 P   | pd                                                                                     d          }|st          d          |t          v rt          d|           t                      }t          ||          }	 t          j        ||t          j	        t          j
                  }n*# t          j        $ r}t          d|           |d}~ww xY wg }t                      }	|D ] \  }
}}}}|d         }d|v r|                    d          d         }	 t          j        |          n.# t           $ r!}t          d	|d         d
|           |d}~ww xY wt"          v s t%          fdt&          D                       rt          d| d|           |s&|s$t)                    rt          d| d|           ||	vrBt+          |          t,          k     r*|                    |           |	                    |           "|st          d|           |S )au  Resolve and validate *host* for one HTTP connect attempt.

    Unlike :func:`is_safe_url`, this is called from the HTTP transport at the
    time the TCP socket is about to be opened.  It returns concrete IP strings
    that the transport can dial directly, closing the DNS-rebinding gap between
    pre-flight validation and connection setup for direct httpx clients.
    rO   rx   z#Blocked request with empty hostnamez&Blocked request to internal hostname: z-Blocked request - DNS resolution failed for: Nr   r   z)Blocked request - unparseable IP address z for hostname c              3       K   | ]}|v V  	d S rz   rg   r{   s     r*   r~   z-_resolved_http_connect_ips.<locals>.<genexpr>  s'      +Z+Z#B#I+Z+Z+Z+Z+Z+ZrJ   z:Blocked request to cloud metadata address during connect: z -> z<Blocked request to private/internal address during connect: z/Blocked request - DNS returned no results for: )r   r   r   r   r   re   r   r   r   r   r   r   setr   rk   r   r   r   r   r   rv   len_MAX_SSRF_CONNECT_IPSappendadd)r   r   r   r   r   r   r   r   safe_ipsseenr   r   r   r   ri   s                 @r*   _resolved_http_connect_ipsr     s    
!!##))++22377H K#$IJJJ%%%#$WX$W$WXXX2444XvFF&dF,f.@
 
		 ?   #FHFF
 
	
 HUUD&/  "Aq(!&==\\#&&q)F	%f--BB 	 	 	'cHQKccYacc 	
 $$$+Z+Z+Z+ZAY+Z+Z+Z(Z(Z$'cXcc[acc   ! 	)9 	nR>P>P 	'exee]cee   #h--2G"G"GOOF###HHV b#$`V^$`$`aaaOs0   +B4 4CCC!D66
E! EE!c                       e Zd ZdefdZ	 	 	 ddedededz  dedz  ded	efd
Z	 	 ddededz  ded	efdZ	ded	dfdZ
dS )_SSRFGuardedAsyncNetworkBackendschemes_by_origin_varc                 >    ddl m}  |            | _        || _        d S )Nr   )AutoBackend)httpcore._backends.autor   _backend_schemes_by_origin_var)selfr   r   s      r*   __init__z(_SSRFGuardedAsyncNetworkBackend.__init__'  /    777777#&;###rJ   Nr   r   timeoutlocal_addresssocket_optionsr   c                 p  K   dd l }| j                            i           }t          |||          }t	          j        t          |||           d {V }	d }
|	D ]I}	 | j                            |||||           d {V c S # |j	        |j
        f$ r}|}
Y d }~Bd }~ww xY w|
|
t          d|           Nr   )r   r   r   z2Blocked request - DNS returned no usable IPs for: )httpcorer   r_   r   r   r   r   r   connect_tcpConnectErrorConnectTimeoutr   r   r   r   r   r   r   r   r   r   ipslast_excri   r   s                r*   r   z+_SSRFGuardedAsyncNetworkBackend.connect_tcp-  s3      	 7;;B??%dD2CDD%&@$fUUUUUUUU%) 	 	B
!]66#"/#1 7            )8+BC    N#$_Y]$_$_```s   $BBBBr$   c                 $   K   t          d          Nz5Blocked Unix socket connection in SSRF-safe transportr   r   r$   r   r   s       r*   connect_unix_socketz3_SSRFGuardedAsyncNetworkBackend.connect_unix_socketL  s       $$[\\\rJ   secondsc                 J   K   | j                             |           d {V  d S rz   r   sleepr   r   s     r*   r   z%_SSRFGuardedAsyncNetworkBackend.sleepT  s4      m!!'***********rJ   NNNNNr   r   r   r   r   r   intfloatr   r   r   rg   rJ   r*   r   r   &  s       <c < < < < !%$("a aa a 	a
 Tza a 
a a a aD !%"	] ]] ] 	]
 
] ] ] ]+5 +T + + + + + +rJ   r   c                       e Zd ZdefdZ	 	 	 ddedededz  dedz  ded	efd
Z	 	 ddededz  ded	efdZ	ded	dfdZ
dS )_SSRFGuardedNetworkBackendr   c                 >    ddl m}  |            | _        || _        d S )Nr   )SyncBackend)httpcore._backends.syncr   r   r   )r   r   r   s      r*   r   z#_SSRFGuardedNetworkBackend.__init__Y  r   rJ   Nr   r   r   r   r   r   c                 >   dd l }| j                            i           }t          |||          }t	          |||          }	d }
|	D ]C}	 | j                            |||||          c S # |j        |j        f$ r}|}
Y d }~<d }~ww xY w|
|
t          d|           r   )
r   r   r_   r   r   r   r   r   r   r   r   s                r*   r   z&_SSRFGuardedNetworkBackend.connect_tcp_  s     	 7;;B??%dD2CDD(tV<<%) 	 	B
}00#"/#1 1      )8+BC    N#$_Y]$_$_```s   A))B:BBr$   c                      t          d          r   r   r   s       r*   r   z._SSRFGuardedNetworkBackend.connect_unix_socket~  s     $$[\\\rJ   r   c                 :    | j                             |           d S rz   r   r   s     r*   r   z _SSRFGuardedNetworkBackend.sleep  s    G$$$$$rJ   r   r   r   rg   rJ   r*   r   r   X  s       <c < < < < !%$("a aa a 	a
 Tza a 
a a a aD !%"	] ]] ] 	]
 
] ] ] ]%U %t % % % % % %rJ   r   requestc                 j    | j         j        }| j         j        }| j         j        }|r||dvri S ||f|iS )N>   r   r   )r   r   r   r   )r   r   r   r   s       r*   _origin_scheme_contextr     sK    ;D;D[F 4<61B#B#B	4L&!!rJ   kwargsc                  x    ddl }ddl}|                    d           G fdd|j                  } |di | S )zLReturn an httpx async transport that pins direct TCP connects to vetted IPs.r   N hermes_ssrf_async_origin_schemesc                   >     e Zd Zdef fdZdedef fdZ xZS )2ssrf_safe_async_http_transport.<locals>._Transporttransport_kwargsc                 l     t                      j        di | t                    | j        _        d S Nrg   )superr   r   _pool_network_backendr   r   	__class__r   s     r*   r   z;ssrf_safe_async_http_transport.<locals>._Transport.__init__  s>    EGG00/000*I%+ +DJ'''rJ   r   r   c                    K                        t          |                    }	 t                                          |           d {V 	                     |           S #                     |           w xY wrz   )r   r   r   handle_async_requestresetr   r   r/   r   r   s      r*   r   zGssrf_safe_async_http_transport.<locals>._Transport.handle_async_request  s      )--.DW.M.MNNE3"WW99'BBBBBBBBB%++E2222%++E2222s   &A$ $A;)r   r   r   r   r   r   __classcell__r   r   s   @r*   
_Transportr     s{        	s 	 	 	 	 	 	 		3c 	3c 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3rJ   r   rg   )contextvarshttpx
ContextVarAsyncHTTPTransportr   r   r   r   r   s       @r*   ssrf_safe_async_http_transportr    s|    LLL'223UVV3 3 3 3 3 3 3U- 3 3 3 :rJ   c                  x    ddl }ddl}|                    d           G fdd|j                  } |di | S )zKReturn an httpx sync transport that pins direct TCP connects to vetted IPs.r   Nhermes_ssrf_origin_schemesc                   >     e Zd Zdef fdZdedef fdZ xZS ),ssrf_safe_http_transport.<locals>._Transportr   c                 l     t                      j        di | t                    | j        _        d S r   )r   r   r   r   r   r   s     r*   r   z5ssrf_safe_http_transport.<locals>._Transport.__init__  s>    EGG00/000*D%+ +DJ'''rJ   r   r   c                                          t          |                    }	 t                                          |                              |           S #                     |           w xY wrz   )r   r   r   handle_requestr   r   s      r*   r  z;ssrf_safe_http_transport.<locals>._Transport.handle_request  sk    )--.DW.M.MNNE3ww--g66%++E2222%++E2222s    A A2)r   r   r   r   r   r  r   r   s   @r*   r   r    s{        	s 	 	 	 	 	 	 		3# 	3# 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3rJ   r   rg   )r   r   r  HTTPTransportr  s       @r*   ssrf_safe_http_transportr    s|    LLL'223OPP3 3 3 3 3 3 3U( 3 3 3 :rJ   	transportr   c                    | t          | di           ni }| |                    dd          rd S |                    d          }|t          |d          st          d          t	                    |_        t          | dd           t          d          dt          d	t          ffd
}|| _        d| _        d S )N__dict___hermes_ssrf_guardedFr   r   z:Unsupported async httpx transport cannot be made SSRF-safer   r   r   c                    K                        t          |                     }	  |            d {V 	                     |           S #                     |           w xY wrz   r   r   r   )r   r/   r   r   s     r*   guarded_handle_async_requestzL_install_ssrf_guard_on_async_transport.<locals>.guarded_handle_async_request  s}      %))*@*I*IJJ	/--g666666666!''....!''....s   A A%T)	getattrr_   hasattrr   r   r   r   r   r  )r  r   statepoolr  r   s    `   @r*   &_install_ssrf_guard_on_async_transportr    s    2;2GGIz2...REEII&<eDD99WD|74);<<|#$`aaa;<QRRD"9.DdKK##$`aaa/C /C / / / / / / / &BI"%)I"""rJ   c                    | t          | di           ni }| |                    dd          rd S |                    d          }|t          |d          st          d          t	                    |_        t          | dd           t          d          dt          d	t          ffd
}|| _        d| _        d S )Nr  r  Fr   r   z4Unsupported httpx transport cannot be made SSRF-safer  r   r   c                                          t          |                     }	  |                               |           S #                     |           w xY wrz   r  )r   r/   r  r   s     r*   guarded_handle_requestz@_install_ssrf_guard_on_transport.<locals>.guarded_handle_request  sc    %))*@*I*IJJ	/!>'**!''....!''....s   
A AT)	r  r_   r  r   r   r   r   r  r  )r  r   r  r  r  r  s    `   @r*    _install_ssrf_guard_on_transportr    s    2;2GGIz2...REEII&<eDD99WD|74);<<|#$Z[[[67LMMDY(8$??N#$Z[[[/ / / / / / / / /  6I%)I"""rJ   clientc                     dd l }|                    d          }t          | di           }t          |                    d          |           d S )Nr   r   r  
_transport)r   r  r  r  r_   r  r   r   r  s       r*   #_install_ssrf_guard_on_async_clientr"    sb    '223UVVFJ++E*		,!6    rJ   c                     dd l }|                    d          }t          | di           }t          |                    d          |           d S )Nr   r  r  r   )r   r  r  r  r_   r!  s       r*   _install_ssrf_guard_on_clientr$    sb    '223OPPFJ++E$		,!6    rJ   c                  F    ddl } |j        di | }t          |           |S )a  Create an ``httpx.AsyncClient`` with connect-time SSRF validation.

    Direct HTTP(S) connections are resolved, validated, and dialed by IP at
    TCP-connect time while the original request hostname is preserved for Host,
    SNI, and certificate verification.  If httpx routes through a proxy, final
    target resolution is delegated to that configured proxy; treat the proxy as
    a trusted egress boundary.
    r   Nrg   )r   AsyncClientr"  r   r   r  s      r*   create_ssrf_safe_async_clientr(  	  s8     LLLU((((F'///MrJ   c                  F    ddl } |j        di | }t          |           |S )z=Create an ``httpx.Client`` with connect-time SSRF validation.r   Nrg   )r   Clientr$  r'  s      r*   create_ssrf_safe_clientr+    s5    LLLU\##F##F!&)))MrJ   responsec           	      @   t          | dd          sdS t          | di           pi }|                    d          }|r9t          t          t          | dd                    t          |                    S t          | dd          }|rt          |j                  S dS )	a  Return the redirect target visible from inside an httpx response hook.

    In ``httpx.AsyncClient`` response event hooks, ``response.next_request`` is
    frequently ``None`` even for a genuine redirect (it is populated later by
    the redirect-following machinery). Relying on ``next_request`` alone means
    an SSRF redirect guard silently never fires: a public URL that 302s to
    ``http://169.254.169.254/`` gets followed anyway. The ``Location`` header,
    however, is already present on the response, so resolve the target from it
    first (handling relative Locations via ``urljoin``) and only fall back to
    ``next_request`` when no ``Location`` header is set.
    is_redirectFNheaderslocationr   rO   next_request)r  r_   r   r   r   )r,  r/  r0  r1  s       r*   redirect_target_from_responser2  "  s     8]E22 th	2..4"G{{:&&H Is78UB7788#h--HHH8^T::L %<#$$$4rJ   )r   N)Fr   rk   loggingr\   r   r   r   typingr   r   urllib.parser   r   r   r   r	   r
   r   utilsr   	getLoggerr   r   r   r+   	frozensetrC   rF   boolrI   r   r   r   
ip_networkr   r   r   rt   r[   rL   __annotations__re   rh   IPv4Addressrl   rv   r   r   r   r   r   r   r   r`   tupler   listr   r   r   r   r  r  r  r  r"  r$  r(  r+  r2  rg   rJ   r*   <module>r?     s-    6      				   				                 [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ ! ! ! ! ! !		8	$	$-F3 -F3 -F -F -F -Fn  )y * * *     0C HSM    ,7C 7D 7 7 7 7 Y      iI*++I))I*++I))I*++I122I/00I122I122!    I)**I122  &I'      &%o66   # t # # #0!D 0! 0! 0! 0!f" " " "y,y/DD     ,`s `t ` ` ` `FGC G G G G G G
SS ST S S S Sl5 5 5 5 5 5R R R R RJ R R RWs W# W$uSRUXX[G[B\ Wad W W W W8S 8 8S 8T#Y 8 8 8 8v/+ /+ /+ /+ /+ /+ /+ /+d/% /% /% /% /% /% /% /%d"C "DsCx#1E,F " " " " S  S        0 s  s        0*c *RU *Z^ * * * *2* *C *TX * * * *2     # $    C C     c c    C HSM      rJ   