
    `gjJN                       U d Z ddlm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mZmZ ddlmZ 	 ddlmZ  ej&                  e      Z ed	      Zd
ed<   d"dZdaded<   d#dZ	 d$	 	 	 	 	 d%dZ	 d$	 	 	 	 	 d&dZd'dZd'dZ	 d$	 	 	 d(dZda ded<   d)dZ!	 d$	 	 	 d*dZ"g dZ#ded<   	 d$	 	 	 d*dZ$	 d$	 	 	 	 	 d+dZ%	 d$	 	 	 	 	 d,dZ&	 d$	 	 	 	 	 d-dZ'	 d$	 	 	 d*d Z(d.d!Z)y# e$ r dZY w xY w)/u  File passthrough registry for remote terminal backends.

Remote backends (Docker, Modal, SSH) create sandboxes with no host files.
This module ensures that credential files, skill directories, and host-side
cache directories (documents, images, audio, screenshots) are mounted or
synced into those sandboxes so the agent can access them.

**Credentials and skills** — session-scoped registry fed by skill declarations
(``required_credential_files``) and user config (``terminal.credential_files``).

**Cache directories** — gateway-cached uploads, browser screenshots, TTS
audio, and processed images.  Mounted read-only so the remote terminal can
reference files the host side created (e.g. ``unzip`` an uploaded archive).

Remote backends call :func:`get_credential_file_mounts`,
:func:`get_skills_directory_mount` / :func:`iter_skills_files`, and
:func:`get_cache_directory_mounts` / :func:`iter_cache_files` at sandbox
creation time and before each command (for resync on Modal).
    )annotationsN)
ContextVar)Path)DictListOptional)cfg_get)get_read_block_error_registered_fileszContextVar[Dict[str, str]]_registered_files_varc                 |    	 t         j                         S # t        $ r i } t         j                  |        | cY S w xY w)zSGet or create the registered credential files dict for the current context/session.)r   getLookupErrorset)vals    I/root/.hermes/venv/lib/python3.12/site-packages/tools/credential_files.py_get_registeredr   +   s=    $((**  !!#&
s    ";;zList[Dict[str, str]] | None_config_filesc                     ddl m}   |        S )Nr   get_hermes_home)hermes_constantsr   r   s    r   _resolve_hermes_homer   9   s    0    c                   t               }t        j                  j                  |       rt        j                  d|        y|| z  }ddlm}  |||      }|rt        j                  d| |       y|j                         }|j                         st        j                  d|       yt        t        j                  d|        y	 t        t        |            }|rt        j                  d	|        y|j!                  d
       d
|  }t        |      t#               |<   t        j                  d||       y# t        $ r t        j                  d|        Y yw xY w)ue  Register a credential file for mounting into remote sandboxes.

    *relative_path* is relative to ``HERMES_HOME`` (e.g. ``google_token.json``).
    Returns True if the file exists on the host and was registered.

    Security: rejects absolute paths and path traversal sequences (``..``).
    The resolved host path must remain inside HERMES_HOME so that a malicious
    skill cannot declare ``required_credential_files: ['../../.ssh/id_rsa']``
    and exfiltrate sensitive host files into a container sandbox.

    Containment alone is not sufficient, because HERMES_HOME is exactly where
    the MASTER credential stores live. A skill legitimately needs its own
    service token (``google_token.json``); it never needs ``.env`` (every
    provider key), ``auth.json`` (all provider tokens and OAuth grants),
    ``mcp-tokens/`` or the Bitwarden plaintext cache. Those are refused via
    the canonical read deny-list (``agent.file_safety.get_read_block_error``)
    — the same guard that stops the agent reading them with ``read_file``, so
    the mount surface cannot hand a skill what the read surface denies it.
    zMcredential_files: rejected absolute path %r (must be relative to HERMES_HOME)Fr   validate_within_dirz1credential_files: rejected path traversal %r (%s)z)credential_files: skipping %s (not found)u|   credential_files: refusing %r — agent.file_safety could not be imported, so the master-store deny-list cannot be consultedu3   credential_files: refusing %r — read guard raisedu   credential_files: refused %r — it is a credential store the agent is denied from reading; a skill may mount its own service token, not the master key files/z%credential_files: registered %s -> %sT)r   ospathisabsloggerwarningtools.path_securityr   resolveis_filedebugr
   errorstr	Exception	exceptionrstripr   )	relative_pathcontainer_basehermes_home	host_pathr   containment_errorresolveddeniedcontainer_paths	            r   register_credential_filer5   >   sY   . '(K 
ww}}]#[	
 m+I 8+I{C?	

   "H@(K #J	

 %c(m4 ' 		
 &--c231]ODN(+HOn%
LL8(NS#  A=	
 	s   D5 5EEc                ,   g }| D ]  }t        |t              r|j                         }nHt        |t              r7|j	                  d      xs |j	                  d      xs dj                         }nl|sot        ||      r||j                  |        |S )zRegister multiple credential files from skill frontmatter entries.

    Each entry is either a string (relative path) or a dict with a ``path``
    key.  Returns the list of relative paths that were NOT found on the host
    (i.e. missing files).
    r    name )
isinstancer)   stripdictr   r5   append)entriesr.   missingentryrel_paths        r   register_credential_filesrA      s     G 
%eS!{{}Ht$		&)DUYYv->D"KKMH'.ANN8$
% Nr   c                    t         t         S g } 	 ddlm} t               } |       }t	        |dd      }t        |t              rddlm} |D ]  }t        |t              s|j                         s%|j                         }t        j                  j                  |      rt        j                  d|       k||z  } |||      }	|	rt        j                  d||	       |j!                         }
|
j#                         sd	| }| j%                  t        |
      |d
        | a t         S # t&        $ r'}t        j                  d|       Y d}~| a t         S d}~ww xY w)z=Load ``terminal.credential_files`` from config.yaml (cached).Nr   )read_raw_configterminalcredential_filesr   z2credential_files: rejected absolute config path %rz8credential_files: rejected config path traversal %r (%s)z/root/.hermes/r0   r4   z8Could not read terminal.credential_files from config: %s)r   hermes_cli.configrC   r   r	   r9   listr$   r   r)   r:   r   r    r!   r"   r#   r%   r&   r<   r*   )resultrC   r/   cfg
cred_filesr   itemrelr0   r1   resolved_pathr4   es                r   _load_config_filesrP      sR     #%F V5*,S*.@A
j$'?" dC(TZZ\**,Cww}}S)PRU ! +c 1I(;I{(S%(V!2 !$-$5$5$7M$,,.+9#)?),]);.<' '4 M	  VQSTUUM	Vs+   AD,  D, 1BD,  $D, ,	E5EEc                 X   i } t               j                         D ]$  \  }}t        |      j                         s || |<   & t	               D ]1  }|d   }|| vst        |d         j                         s*|d   | |<   3 | j                         D cg c]
  \  }}||d c}}S c c}}w )zReturn all credential files that should be mounted into remote sandboxes.

    Each item has ``host_path`` and ``container_path`` keys.
    Combines skill-registered files and user config.
    r4   r0   rF   )r   itemsr   r&   rP   )mountsr4   r0   r?   cphps         r   get_credential_file_mountsrV      s      F &5%6%<%<%> /!		?""$%.F>"/ $% ,#$VU;%7 8 @ @ B{+F2J, llnB B/  s   B&c                   g }t               }|dz  }|j                         r1t        |      }|j                  || j	                  d       dd       	 ddlm} t         |             D ]I  \  }}|j                         st        |      }|j                  || j	                  d       d| d       K 	 |S # t        $ r Y |S w xY w)aM  Return mount info for all skill directories (local + external).

    Skills may include ``scripts/``, ``templates/``, and ``references/``
    subdirectories that the agent needs to execute inside remote sandboxes.

    **Security:** Bind mounts follow symlinks, so a malicious symlink inside
    the skills tree could expose arbitrary host files to the container.  When
    symlinks are detected, this function creates a sanitized copy (regular
    files only) in a temp directory and returns that path instead.  When no
    symlinks are present (the common case), the original directory is returned
    directly with zero overhead.

    Returns a list of dicts with ``host_path`` and ``container_path`` keys.
    The local skills dir mounts at ``<container_base>/skills``, external dirs
    at ``<container_base>/external_skills/<index>``.
    skillsr   /skillsrF   r   get_external_skills_dirs/external_skills/)	r   is_dir_safe_skills_pathr<   r,   agent.skill_utilsr[   	enumerateImportError)r.   rS   r/   
skills_dirr0   r[   idxext_dirs           r   get_skills_directory_mountre      s    & F&(Kx'J%j1	"!/!6!6s!; <GD
 	
>%&>&@A 	LC~~-g6	!*)7)>)>s)C(DDUVYUZ&[ 	 M  Ms   ,B9 5B9 9	CCzPath | None_safe_skills_tempdirc                t  
 | j                  d      D cg c]  }|j                         s| }}|st        |       S |D ],  }t        j	                  d|t        j                  |             . ddl}ddlddl	}t        r+t        j                         rj                  t        d       t        |j                  d            

a
| j                  d      D ]  }|j                         r|j                  |       }
|z  }|j                         r|j!                  dd	       N|j#                         s_|j$                  j!                  dd	       j'                  t        |      t        |              
fd
}	|j)                  |	       t        j+                  d
       t        
      S c c}w )z@Return *skills_dir* if symlink-free, else a sanitized temp copy.*z:credential_files: skipping symlink in skills dir: %s -> %sr   NTignore_errorszhermes-skills-safe-)prefix)parentsexist_okc                 N     j                         rj                   d       y y )NTri   )r]   rmtree)safe_dirshutils   r   _cleanupz#_safe_skills_path.<locals>._cleanupI  s"    ??MM($M7 r   z8credential_files: created symlink-safe skills copy at %s)rglob
is_symlinkr)   r"   r#   r   readlinkatexitrq   tempfilerf   r]   ro   r   mkdtemprelative_tomkdirr&   parentcopy2registerinfo)rb   psymlinkslinkrv   rw   rL   rM   targetrr   rp   rq   s             @@r   r^   r^   '  sr    &++C0CaALLNCHC: 0SR[[.	00   4 ; ; =*$?H$$,A$BCH#  % 	1??z*C;;=LLL5\\^MMt<LLTCK0	18 OOH
KKJHUx=I Ds
   F5F5c                   g }t               }|dz  }|j                         r~| j                  d       d}|j                  d      D ]V  }|j	                         s|j                         s$|j                  |      }|j                  t        |      | d| d       X 	 ddl	m
} t         |             D ]  \  }}	|	j                         s| j                  d       d| }|	j                  d      D ]V  }|j	                         s|j                         s$|j                  |	      }|j                  t        |      | d| d       X  	 |S # t        $ r Y |S w xY w)	a>  Yield individual (host_path, container_path) entries for skills files.

    Includes both the local skills dir and any external dirs configured via
    skills.external_dirs.  Skips symlinks entirely.  Preferred for backends
    that upload files individually (Daytona, Modal) rather than mounting a
    directory.
    rX   r   rY   rh   rF   r   rZ   r\   )r   r]   r,   rs   rt   r&   ry   r<   r)   r_   r[   r`   ra   )
r.   rI   r/   rb   container_rootrL   rM   r[   rc   rd   s
             r   iter_skills_filesr   R  sw    $&F&(Kx'J*11#67w?$$S) 	D "":.CMM Y%3$4AcU"; 		>%&>&@A 	LC>># . 5 5c :;;LSERNc* ??$DLLN&&w/!$T)7(8#&? 			 M  Ms   !B/E 	E E ))zcache/documentsdocument_cache)zcache/imagesimage_cache)zcache/audioaudio_cache)zcache/videosvideo_cache)zcache/screenshotsbrowser_screenshots)z	cache/web	web_cache)zcache/delegationdelegation_cachezlist[tuple[str, str]]_CACHE_DIRSc                    ddl m} g }t        D ]R  \  }} |||      }|j                         s | j	                  d       d| }|j                  t        |      |d       T |S )a   Return mount entries for each cache directory that exists on disk.

    Used by Docker to create bind mounts.  Each entry has ``host_path`` and
    ``container_path`` keys.  The host path is resolved via
    ``get_hermes_dir()`` for backward compatibility with old directory layouts.
    r   get_hermes_dirr   rF   )r   r   r   r]   r,   r<   r)   )r.   r   rS   new_subpathold_namehost_dirr4   s          r   get_cache_directory_mountsr     st     0#%F!, X!+x8?? . 5 5c :;1[MJNMM ]"0  Mr   c                    t        |       }t        |      D ]J  }t        |d         }	 |j                  |      }t	        j
                  |d   |j                               c S  y# t        $ r Y Yw xY w)a  Map a host cache path to its mounted path under *container_base*.

    Returns the POSIX container path when *host_path* lives under one of the
    auto-mounted cache directories, otherwise ``None``.  Backend-agnostic: the
    caller decides which ``container_base`` applies (Docker ``/root/.hermes``,
    SSH ``<remote_home>/.hermes``, etc.) and whether translation is wanted.
    Always joins with ``posixpath`` because container/remote paths are POSIX
    regardless of the host OS.
    r.   r0   r4   N)r   r   ry   
ValueError	posixpathjoinas_posix)r0   r.   r    mountr   rM   s         r   map_cache_path_to_containerr     s     	?D+>J Gk*+	""8,C ~~e$45s||~FFG   		s   A&&	A21A2c                   t         j                  j                  dd      dk7  r| S t        |       }t	        |      D ]2  }	 |j                  |d         }t        t        |d         |z        c S  | S # t        $ r Y Bw xY w)us  Translate a sandbox/container cache path back to its host path.

    Inverse of :func:`to_agent_visible_cache_path`. Returns the input unchanged
    when the active backend is not Docker, or when the path is not under any
    auto-mounted cache directory — the caller then treats a still-container
    path as "no host file" and falls back to an in-container read.
    TERMINAL_ENVlocaldockerr   r4   r0   )r   environr   r   r   ry   r   r)   )r4   r.   r    r   rM   s        r   from_agent_visible_cache_pathr     s     
zz~~ng.(:D+>J 3	""5)9#:;C 4k*+c1223   		s   A44	B ?B c                r    t         j                  j                  dd      dk7  r| S t        | |      }||S | S )a  Translate a host cache path to its mounted path inside the sandbox.

    Returns the input unchanged if it is not under any auto-mounted cache
    directory, or if the active terminal backend does not require path
    translation (only Docker for now).
    r   r   r   r   )r   r   r   r   )r0   r.   mappeds      r   to_agent_visible_cache_pathr     s=     
zz~~ng.(:(>RF'66Y6r   c                f   ddl m} g }t        D ]  \  }} |||      }|j                         s | j	                  d       d| }|j                  d      D ]V  }|j                         s|j                         s$|j                  |      }|j                  t        |      | d| d       X  |S )zReturn individual (host_path, container_path) entries for cache files.

    Used by Modal to upload files individually and resync before each command.
    Skips symlinks.  The container paths use the new ``cache/<subdir>`` layout.
    r   r   r   rh   rF   )r   r   r   r]   r,   rs   rt   r&   ry   r<   r)   )	r.   r   rI   r   r   r   r   rL   rM   s	            r   iter_cache_filesr     s     0#%F!, X!+x8 *11#67qFNN3' 	D ""8,CMM Y%3$4AcU"; 		 Mr   c                 4    t               j                          y)z8Reset the skill-scoped registry (e.g. on session reset).N)r   clear r   r   clear_credential_filesr   	  s    r   )returnzDict[str, str])r   r   )z/root/.hermes)r-   r)   r.   r)   r   bool)r=   rH   r.   r)   r   z	List[str])r   List[Dict[str, str]])r.   r)   r   zlist[Dict[str, str]])rb   r   r   r)   )r.   r)   r   r   )r0   r)   r.   r)   r   zOptional[str])r4   r)   r.   r)   r   r)   )r0   r)   r.   r)   r   r)   )r   None)*__doc__
__future__r   loggingr   r   contextvarsr   pathlibr   typingr   r   r   rG   r	   agent.file_safetyr
   ra   	getLogger__name__r"   r   __annotations__r   r   r   r5   rA   rP   rV   re   rf   r^   r   r   r   r   r   r   r   r   r   r   r   <module>r      s  ( #  	  "  ' ' % 6 
		8	$ 5??R4S 1 S .2* 1 *VVV 
Vv * 2*Z6 ****Z %) k ((X *+++h&"  *4 * 4 * 	4 *777 	7, *6Q    s   C# #C-,C-