
    `gj|O                     z   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jB                  d       ejB                  d      fZ" edh      Z# ejB                  d      Z$da%da&ee'd<   defdZ(d)dZ)dejT                  ejV                  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%Z/dedefd&Z0d'ede	e   fd(Z1y)*uL  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 (documented, not fixable at pre-flight level):
  - 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. Fixing this requires connection-level validation (e.g.
    Python's Champion library or an egress proxy like Stripe's Smokescreen).
  - 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                 x   t        | t              s| S | j                         }|s|S t        j                  dd|      }	 t        |      }|j                  j                         dvr|S |j                  }|j                  }|r9	 |j                  d      j                  d      }||k7  r|j                  ||d      }t        |j                   d      }t        |j"                  d	      }t        |j$                  d	      }t'        |j                  ||||f      S # t        $ r |cY S w xY w# t        $ r |}Y w xY w)
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	            C/root/.hermes/venv/lib/python3.12/site-packages/tools/url_safety.pynormalize_url_for_requestr+   (   s-    c3

))+C
 &&4eS
AC# }}$55
]]FH	"!077@J !^^Hj!<F#45D&,,%78EV__+=>Hv}}fdE8DEE)  
  	"!J	"s#   D  D+ D('D(+D98D9>   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y	 t        | j                               }|j
                  j                         dvs|j                  syt        |j                  d      D ]+  \  }}|s	t        |      j                         t        v s)|c S  y# t        $ r Y yw xY w)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   w   s     c33c>#))+& }}$55V\\E 
UWS\'')-IIJ   s   B 	B+*B+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.cnz100.64.0.0/10F_cached_allow_privatec                     t         rt        S da dat        j                  dd      j	                         j                         } | dv rdat        S | dv rt        S 	 ddlm}  |       }|j                  d	i       }t        |t              r$t        |j                  d
      d      rdat        S |j                  di       }t        |t              r$t        |j                  d
      d      rdat        S t        S # t        $ r Y t        S w xY w)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_resolvedrK   osgetenvr   r   hermes_cli.configrU   getr   dictr   	Exception)env_valrU   cfgsecrY   s        r*   _global_allow_private_urlsrd      s    $$"! ii3R8>>@FFHG&& $$$&&$$5ggj"%c4 _GG()5&
 %)!(('')R(gt$KK,-u*
 %)!((
 ! 	    	s   AC9 -AC9 9	D
	D
c                      da day)u+   Reset the cached toggle — only for tests.FN)rZ   rK    rJ   r*   _reset_allow_private_cacherg      s     $!rJ   ipc                    t        | t        j                        rt| j                  h| j                  }|j                  xsN |j
                  xs@ |j                  xs2 |j                  xs$ |j                  xs |j                  xs |t        v S | j                  s$| j
                  s| j                  s| j                  ry| j                  s| j                  ry| t        v ryy)z<Return True if the IP should be blocked for SSRF protection.TF)r   	ipaddressIPv6Addressipv4_mapped
is_privateis_loopbackis_link_localis_reservedis_multicastis_unspecified_CGNAT_NETWORK)rh   embedded_ips     r*   _is_blocked_ipru     s     "i++,1Knn&& .+*A*A .)).-8-D-D.((.,7,F,F. ~-	/ 
}}"*:*:bnn	"++	^rJ   c                   	
 	 t        |       }|j                  xs dj                         j                         j	                  d      }|sy|t
        v rt        j                  d|       y	 t        j                  |      		8	t        v st        	fdt        D              rt        j                  d|       yy	 t        j                  |dt        j                   t        j"                        }|D ]t  \  }}}}}|d	   }d
|v r|j'                  d
      d	   }	 t        j                  |      

t        v st        
fdt        D              s]t        j                  d||        y y# t        $ r d	Y w xY w# t        j$                  $ r Y yw xY w# t        $ r t        j                  d|d	   |       Y w xY w# t(        $ r!}t        j+                  d| |       Y d}~y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``.
    rN   .Fz?Blocked request to internal hostname (always-blocked floor): %sTNc              3   &   K   | ]  }|v  
 y wNrf   .0netrh   s     r*   	<genexpr>z(is_always_blocked_url.<locals>.<genexpr>J  s      0!c	0   zDBlocked request to cloud metadata address (always-blocked floor): %sr   %u>   Unparseable IP address %r for hostname %s — skipping addressc              3   &   K   | ]  }|v  
 y wry   rf   )r{   r|   resolveds     r*   r}   z(is_always_blocked_url.<locals>.<genexpr>g  s      6$'C6r~   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warningrj   
ip_addressr   _ALWAYS_BLOCKED_IPSany_ALWAYS_BLOCKED_NETWORKSsocketgetaddrinfo	AF_UNSPECSOCK_STREAMgaierrorsplitr`   debug)r   r(   r   	addr_info_family_sockaddrip_strexcrh   r   s            @@r*   is_always_blocked_urlr     s   :C#OO)r00288:AA#F ))NNQ 	%%h/B >((C 0%=0 - 1
 	**$ 0 0&2D2DI +4 	&GQ1ha[Ff}c*1-$//7 ..# 6+C6 3 7	 %	( Y  	B	*  		  _aijkalnvw    	=sCH	s   AF> F> -E+ 8F> =4E< 1(F> F/ F> F> )F> +E96F> 8E99F> <FF> FF> #F;8F> :F;;F> >	G(G##G(r   r   c                      |dk(  xr | 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                  xs dj                         j                         j	                  d      }|j
                  xs dj                         j                         }|dvrt        j                  d|xs d       y|sy|t        v rt        j                  d|       yt               }t        ||      }	 t        j                  |dt        j                  t        j                        }|D ]  \  }}}}}	|	d
   }
d|
v r|
j!                  d      d
   }
	 t#        j$                  |
      t(        v st+        fdt,        D              rt        j                  d||
        y|rx|r{t/              st        j                  d||
        y |rt        j1                  d|       y|rt        j1                  d|       y# t        j                  $ r t        j                  d	|       Y yw xY w# t&        $ r t        j                  d|	d
   |       Y  yw xY w# t2        $ r!}t        j                  d| |       Y d}~y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.
    rN   rw   >   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  
 y wry   rf   rz   s     r*   r}   zis_safe_url.<locals>.<genexpr>  s     /^cc	/^r~   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   rd   r   r   r   r   r   r   r   rj   r   r   r   r   r   ru   r   r`   )r   r(   r   r   allow_all_privateallow_private_ipr   familyr   r   r   r   rh   s               @r*   is_safe_urlr     sF   H#OO)r00288:AA#F--%2,,.446**NNKVM`W`a ))NNExP 7886J	**8T6;K;KVM_M_`I *3 	%FAq!Xa[Ff}c*1-))&1 ((C/^E]/^,^If $-=.QSBTKf 1	4 LL]  LLS
 W  	 NNNPXY		  ^`hij`kmuvB   	NPSUXY	s   BH/ H/ H/ 6H/ 4G (H/ *H?8H/ 8H/ ;H/ >H/ 
H/ #H/ =H/ )H?H/ HH/ #H,(H/ +H,,H/ /	I8IIc                 R   K   t        j                  t        |        d{   S 7 w)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4s   '%'responsec           	         t        | dd      syt        | di       xs i }|j                  d      }|r)t        t        t        | dd            t        |            S t        | dd      }|rt        |j                        S y)	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   rN   next_request)getattrr^   r   r   r   )r   r   r   r   s       r*   redirect_target_from_responser     s{     8]E2h	2.4"G{{:&Hs78UB78#h-HH8^T:L<##$$rJ   )r   N)2__doc__rj   loggingr[   r   r   r   typingr   r   urllib.parser   r   r   r   r	   r
   r   utilsr   	getLogger__name__r   r   r+   	frozensetrC   rF   boolrI   r   r   r   
ip_networkr   r   rs   rZ   rK   __annotations__rd   rg   IPv4Addressrk   ru   r   r   r   r   r   rf   rJ   r*   <module>r      sg  2   	   	   [ [ [ !			8	$-F3 -F3 -Fn  ) *   0C HSM ,7C 7D 7      I*+I)I*+I)I*+I12I/0I12I12!   I)*I12  &'   &%%o6   # t #0!D 0!f"y,,y/D/DD  ,`s `t `FGC G G G
SS ST Sl5 5 5C HSM rJ   