
    `gjw8                       d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZ dZ G d d	e      Z G d
 de      Z G d de      Z G d de      Z G d de      Z G d de      Ze G d d             Ze G d d             Z ej.                  d      Zd#dZd$dZd%dZd&dZd'dZd(dZd)dZd*d Z d+d!Z!d,d"Z"y)-u#  Single resolver for every vision_analyze image source -> bytes + mime.

All source handling (data:/http(s)/file/local/container) funnels through
:func:`resolve_image_source` so size and magic-byte checks are enforced exactly
once.  Returns raw bytes (not a path): the downstream step is base64 -> data URL
(RFC 2397) and provider base64 content blocks.

Security (terminal-backend confinement, GHSA-gpxw-6wxv-w3qq): under a non-local
terminal backend the file tools are confined to the sandbox (SECURITY.md 2.2),
but vision read images host-side. This resolver enforces the same boundary:

  * local backend            -> read any host path (chosen posture, unchanged)
  * non-local backend:
      path in a media cache   -> host-read (the gateway/download caches live on
                                 the host and are bind-mounted into the sandbox)
      path anywhere else      -> read the bytes *inside the sandbox* via exec-read
                                 (the agent can already ``cat`` any container file;
                                 this stays within the sandbox boundary and never
                                 reaches the host's ``/etc/passwd`` / ``~/.ssh``).

So a prompt-injected ``vision_analyze('/etc/passwd')`` under Docker reads the
*container's* file (what every other tool sees), not the host's — no escape —
while container-only images (tmpfs ``/workspace``, root-owned) are still
deliverable. This is the unified delivery + confinement model: the same
mechanism that fixes "vision can't see container files" also closes the escape.
    )annotationsN)	dataclass)Path)Optionali   c                  (     e Zd Zdddd fdZ xZS )ImageResolutionError srcoriginc               B    t         |   |       ||c| _        | _        y )N)super__init__r   r   )selfmessager   r   	__class__s       E/root/.hermes/venv/lib/python3.12/site-packages/tools/image_source.pyr   zImageResolutionError.__init__/   s     ! #V$+    )r   strr   r   r   r   )__name__
__module____qualname__r   __classcell__)r   s   @r   r   r   .   s    35R , ,r   r   c                      e Zd Zy)UnsupportedSchemeNr   r   r    r   r   r   r   4       r   r   c                      e Zd Zy)SourceUnsafeNr   r   r   r   r    r    8   r   r   r    c                      e Zd Zy)SourceTooLargeNr   r   r   r   r"   r"   <   r   r   r"   c                      e Zd Zy)SourceNotFoundNr   r   r   r   r$   r$   @   r   r   r$   c                      e Zd Zy)
NotAnImageNr   r   r   r   r&   r&   D   r   r   r&   c                      e Zd ZU dZded<   y)ResolveContextNOptional[str]task_id)r   r   r   r*   __annotations__r   r   r   r(   r(   H   s    !G]!r   r(   c                  ,    e Zd ZU ded<   ded<   ded<   y)ResolvedImagebytesdatar   mimer   N)r   r   r   r+   r   r   r   r-   r-   M   s    
K
IKr   r-   z^[A-Za-z][A-Za-z0-9+.\-]*://c                r  K   t        | t              r| j                         st        dt        |             | j                         }|j	                  d      rt        |      \  }}t        ||d|      S |j	                  d      r9t        |      }|rt        ||      t        t        |       d {   dd|      S t        j                  |      r,|j                         j	                  d      st        d	|      |j                         j	                  d      r|t        d      d  n|}t        t         j"                  j%                  |            }t'        ||      }|`|j)                         rP	 d
dlm}	 |		  |	t        |             t3        j4                  |j6                         d {   }t        |dd|      S t9               rt        d| d|d      t;        |||       d {   S 7 C# t.        $ r d }	Y w xY w# t0        $ r}
t        t        |
      |d      d }
~
ww xY w7 7 Bw)Nzimage_url is requiredr   data:r/   )zhttp://zhttps://r	   httpzfile://zgUnrecognized image source scheme. Use an http(s) URL, a local file path, a file:// URI, or a data: URL.r   )raise_if_read_blockedfiler
   zimage file not found: '')
isinstancer   stripr$   
startswith_resolve_data_url	_finalize_http_block_reasonr    _download_to_bytes
_SCHEME_REmatchlowerr   lenr   ospath
expanduser_permitted_host_read_targetis_fileagent.file_safetyr5   	Exception
ValueErrorasyncio	to_thread
read_bytes_is_local_terminal_backend_resolve_container_fallback)r   ctxsr/   r0   reason	candidatephost_targetr5   excs              r   resolve_image_sourcerW   Y   s    c3syy{4#c(CC		A||G&q)
dtVQ//||+,#A&v1--1!44b&!DD1779#7#7	#B8
 	
 '(ggi&:&:9&E#i./"1IRWW	*+A .a5K;#6#6#8	)? !,C%c+&67 &&{'='=>>r61--!# 6qc;6RR -QQ777[ 5:  	)$(!	)
  C"3s86BBC> 8sy   B2H74G75CH77G: =H7 H "H73H34>H72H53H7:HH7HH7	H0H++H00H75H7c                   | j                  d      \  }}}d|vrt        d| d d       |t        d      d  j                  dd      d	   j	                         xs d
}t        |      dz  dz  t
        kD  rt        d| d d       	 t        j                  |d      }||fS # t        $ r}t        d| | d d       d }~ww xY w)N,z;base64z data: URL must be base64-encoded@   r2   r3   ;   r   zapplication/octet-stream      zdata: URL exceeds size limitTvalidatezinvalid base64 in data: URL: )
	partitionr&   rB   splitr9   _MAX_INGEST_BYTESr"   base64	b64decoderI   )rQ   header_payloaddeclaredr/   rV   s          r   r;   r;      s    S)FAw;3BHHc'lm$**3215;;=[A[HGqQ!22;3BHHL$7 >  L8>AcrFKKLs   B! !	C*B==Cc                l    ddl m} ddlm}  ||       sy ||       }|r|j	                  d      xs dS y)u	  Return a human-readable block reason, or None when the URL is allowed.

    Pre-flight short-circuit: policy-blocked URLs are refused BEFORE any
    network I/O. ``_download_image`` re-checks policy internally (per attempt
    and against the final redirect target) — that second evaluation is
    intentional, not redundant: this one guarantees no bytes move for a
    blocked URL; the inner one covers redirects and non-resolver callers.
    Preserves the specific website-policy message so the agent sees *why*.
    r   )is_safe_url)check_website_accesszblocked: unsafe or private URLr   zblocked by website policyN)tools.url_safetyrk   tools.website_policyrl   get)urlrk   rl   blockeds       r   r=   r=      s:     -9s/"3'G{{9%D)DDr   c                  K   dd l }ddlm} |j                  dd      5 }t	        |j
                        }d d d        	  ||        d {    t        j                  |j                         d {   |j                  d       S # 1 sw Y   TxY w7 I7 $# t        $ r}t        t        |      | d	      d }~ww xY w# j                  d       w xY ww)
Nr   )_download_imagez.imgF)suffixdeleteT)
missing_okr4   r
   )tempfiletools.vision_toolsrs   NamedTemporaryFiler   namerK   rL   rM   unlinkPermissionErrorr    r   )rp   rw   rs   tftmprV   s         r   r>   r>      s     2		$	$F5	$	A R277m$c3'''&&s~~66 	

d
#  	(6 =3s8V<<= 	

d
#sn   CB	CB B&B 2B3B 6C	BCB B 	B>"B99B>>C CCc                 j    t        j                  dd      j                         j                         dv S )zTrue when the terminal backend runs directly on the host.

    Mirrors ``tools.browser_tool._is_local_backend`` and terminal_tool's own
    dispatch, which key off ``TERMINAL_ENV``.
    TERMINAL_ENVlocal)r   r	   )rC   getenvr9   rA   r   r   r   rN   rN      s,     99^W-335;;=NNr   c                 P    ddl m}   |        }|dz  |dz  |dz  |dz  |dz  |dz  gS )	a-  Agent-managed media cache directories under HERMES_HOME (host side).

    The only host paths vision may read under a non-local backend: gateway-
    downloaded inbound media and the tools' own URL-download temp dirs. Covers
    the consolidated ``cache/`` layout and the legacy flat directories.
    r   )get_hermes_homecacheimage_cacheaudio_cachevideo_cachetemp_vision_imagestemp_video_files)hermes_constantsr   )r   homes     r   _media_cache_rootsr      sJ     1Dw}}}##!! r   c                ^   t               r	 | j                         S ddlm} t         |t        |                   }	 |j                         }t               D ]%  }	 |j                  |j                                |c S  y# t        $ r | cY S w xY w# t        $ r Y yw xY w# t        $ r Y Tw xY w)a  Return the host path to read, or ``None`` if a host read is not permitted.

    - Local backend: any path is permitted (chosen posture). Returns ``p``.
    - Non-local backend: permitted only if the path resolves inside a media
      cache root. A container-visible cache path (e.g. ``/root/.hermes/cache/
      images/x.png``) is first translated back to its host mount; anything that
      is not under a cache returns ``None`` so the caller routes it to the
      in-sandbox exec-read instead of reading the host filesystem.
    r   )from_agent_visible_cache_pathN)
rN   resolverI   tools.credential_filesr   r   r   r   relative_torJ   )rT   rP   r   host_candidaterealroots         r   rF   rF      s     "#	99; E7A?@N%%' #$ 	T\\^,K !  	H	    		s4   B  B  B  BB	BB 	B,+B,c                D    | sy 	 ddl m}  ||       S # t        $ r Y y w xY w)Nr   )get_active_env)tools.terminal_toolr   rI   )r*   r   s     r   _get_active_envr     s-    6g&& s    	c                L  K   ddl }ddl}t        |j                        }|t	        d|  d|d      |j                  t        |             } |j                  |j                  dt        dz    d	| d
       d{   }|j                  dd      dk7  rt	        d|  d|d      	 t        j                  |j                  dd      d      }t        |      t        kD  rt!        d|d      t#        |dd|      S 7 # t        $ r}	t        d|  d|	 |      d}	~	ww xY ww)a  Read the image bytes inside the sandbox (fail-closed when none exists).

    Reached when a host read is not permitted or the host file is absent. The
    agent can already ``cat`` any container file (file_operations.py reads
    root-owned mode-600 files this way), so this stays within the same sandbox
    boundary and never touches the host filesystem. ``--`` stops a leading-dash
    path from being parsed as a ``base64`` option; ``base64 -w0`` is GNU-only,
    so pipe through ``tr -d`` for BusyBox.

    Fail-closed: if there is no active sandbox env we refuse rather than falling
    back to a host read, so a non-cache host path under a sandbox never leaks.
    r   Nr7   z[' is not reachable inside the sandbox and no active sandbox session is available to read it	containerr
   zhead -c r\   z < z | base64 | tr -d '\n'
returncodezcould not read 'z' inside the sandboxoutputr	   Tr_   z%sandbox returned non-image data for 'z': r2   image exceeds size limit)rK   shlexr   r*   r$   quoter   rL   executerc   ro   rd   re   rI   r&   rB   r"   r<   )
rT   rP   r   rK   r   envqpresr/   rV   s
             r   rO   rO     sJ     
#++
&C
{s . /K) 	) 
SV	B!!!
$q()RD0GHJ JC ww|Q1$/s2FGSYdeeW" 5E 4y$$7SUUT2{C00J  W@3seLRUVVWs6   A:D$<C>=+D$)'D  /D$ 	D!	DD!!D$c                    ddl m} t        |       t        kD  rt	        d||       ||       }|1d| dd j                         v rt        | d|	      S t        d
||      t        | ||	      S )u(  Intrinsic-correctness chokepoint: ingest byte cap + magic-byte sniff.

    The cap here is the generous 50MB *ingest* budget, not the 20MB provider
    payload cap — a 20-50MB image must survive this step so the call site can
    resize it under the payload cap. See ``_MAX_INGEST_BYTES``.
    r   )"_detect_image_mime_type_from_bytesr   r
   Ns   <svgi   zimage/svg+xml)r/   r0   r   z source is not a recognized image)rx   r   rB   rc   r"   rA   r-   r&   )r/   declared_mimer   r   r   sniffeds         r   r<   r<   ?  s{     F
4y$$7SPP06Gd5Dk'')) !dPP;VTTd@@r   )r   r   rP   r(   returnr-   )rQ   r   r   ztuple[bytes, str])rp   r   r   r)   )rp   r   r   r.   )r   bool)r   list)rT   r   rP   r(   r   zOptional[Path])r*   r)   )rT   r   rP   r(   r   r   r   r-   )
r/   r.   r   r   r   r   r   r   r   r-   )#__doc__
__future__r   rK   rd   rC   redataclassesr   pathlibr   typingr   rc   rI   r   r   r    r"   r$   r&   r(   r-   compiler?   rW   r;   r=   r>   rN   r   rF   r   rO   r<   r   r   r   <module>r      s   4 #   	 	 !   % ,9 ,	, 		' 		) 		) 		% 	 " " "    RZZ78
88v*$"O(@+1\Ar   