
    ߩ^jJN                       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 n# e$ r dZY nw xY w ej        e          Z ed	          Zd
ed<   d4dZdaded<   d5dZ	 d6d7dZ	 d6d8dZd9dZd9dZ	 d6d:d!Zda d"ed#<   d;d%Z!	 d6d<d&Z"g d'Z#d(ed)<   	 d6d<d*Z$	 d6d=d-Z%	 d6d>d/Z&	 d6d?d0Z'	 d6d<d1Z(d@d3Z)dS )Au  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_varreturnDict[str, str]c                     	 t                                           S # t          $ r! i } t                               |            | 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    8/home/ice/.hermes/hermes-agent/tools/credential_files.py_get_registeredr   +   sV    $((***    !!#&&&


s    (AAzList[Dict[str, str]] | None_config_filesr   c                 "    ddl m}   |             S )Nr   get_hermes_home)hermes_constantsr   r   s    r   _resolve_hermes_homer   9   s"    000000?    /root/.hermesrelative_pathstrcontainer_baseboolc                T   t                      }t          j                            |           rt                              d|            dS || z  }ddlm}  |||          }|rt                              d| |           dS |                                }|	                                st          
                    d|           dS t          t                              d|            dS 	 t          t          |                    }n,# t          $ r t                              d	|            Y dS w xY w|rt                              d
|            dS |                    d           d|  }t          |          t#                      |<   t          
                    d||           dS )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)Nu|   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
   errorr   	Exception	exceptionrstripr   )	r   r    hermes_home	host_pathr$   containment_errorresolveddeniedcontainer_paths	            r   register_credential_filer9   >   s   . '((K 
w}}]## [	
 	
 	
 um+I 877777++I{CC ?	
 	
 	

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

 u%c(mm44   A=	
 	
 	
 uu	
  ' 		
 	
 	
 u&--c22DD]DDN(+HOn%
LL8(NSSS4s   ,D	 	%D21D2entrieslist	List[str]c                b   g }| D ]}t          |t                    r|                                }nUt          |t                    r?|                    d          p|                    d          pd                                }n|st          ||          s|                    |           |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   r9   append)r:   r    missingentryrel_paths        r   register_credential_filesrG      s     G 
% 
%eS!! 	{{}}HHt$$ 			&))DUYYv->->D"KKMMHH 	'.AA 	%NN8$$$Nr   List[Dict[str, str]]c                 0   t           t           S g } 	 ddlm} t                      } |            }t	          |dd          }t          |t                    rddlm} |D ]}t          |t                    r|
                                r|
                                }t          j                            |          rt                              d|           z||z  } |||          }	|	rt                              d||	           |                                }
|
                                r*d	| }|                     t          |
          |d
           n2# t&          $ r%}t                              d|           Y d}~nd}~ww xY w| a t           S )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/r4   r8   z8Could not read terminal.credential_files from config: %s)r   hermes_cli.configrJ   r   r	   r@   r;   r+   r$   r   rA   r&   r'   r(   r)   r*   r,   r-   rC   r0   )resultrJ   r3   cfg
cred_filesr$   itemrelr4   r5   resolved_pathr8   es                r   _load_config_filesrV      s     #%F V555555*,,oS*.@AA
j$'' 	??????"  dC(( TZZ\\ **,,Cw}}S)) !PRU   ! +c 1I(;(;I{(S(S%( !V!2   !$-$5$5$7$7M$,,.. )?#)?)?),]););.<' '     V V VQSTUUUUUUUUV Ms   E
E 
F'FFc                 x   i } t                                                      D ]+\  }}t          |                                          r|| |<   ,t	                      D ]@}|d         }|| vr2t          |d                                                   r|d         | |<   Ad |                                 D             S )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.
    r8   r4   c                    g | ]
\  }}||d S )rM    ).0cphps      r   
<listcomp>z.get_credential_file_mounts.<locals>.<listcomp>   s4       B B//  r   )r   itemsr   r-   rV   )mountsr8   r4   rE   r[   s        r   get_credential_file_mountsr`      s      F &5%6%6%<%<%>%> / /!		??""$$ 	/%.F>" $%% , ,#$VU;%7 8 8 @ @ B B{+F2J llnn   r   list[Dict[str, str]]c                   g }t                      }|dz  }|                                r=t          |          }|                    ||                     d           dd           	 ddlm} t           |                      D ]X\  }}|                                r?t          |          }|                    ||                     d           d| d           Yn# t          $ r Y nw xY w|S )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%   /skillsrM   r   get_external_skills_dirs/external_skills/)	r   is_dir_safe_skills_pathrC   r2   agent.skill_utilsrf   	enumerateImportError)r    r_   r3   
skills_dirr4   rf   idxext_dirs           r   get_skills_directory_mountrp      sI   & F&((Kx'J %j11	"!/!6!6s!;!;DDD
 
 	 	 	
>>>>>>%&>&>&@&@AA 	 	LC~~ -g66	!*)7)>)>s)C)C&[&[VY&[&[    	     Ms   (A6C 
C,+C,zPath | None_safe_skills_tempdirrm   c                  	
 d |                      d          D             }|st          |           S |D ]0}t                              d|t	          j        |                     1ddl}ddl
ddl}t          r5t          
                                r
                    t          d           t          |                    d	                    		a	|                      d          D ]}|                                r|                    |           }	|z  }|
                                r|                    dd
           ]|                                rL|j                            dd
           
                    t          |          t          |                     	
fd}|                    |           t                              d	           t          	          S )z@Return *skills_dir* if symlink-free, else a sanitized temp copy.c                :    g | ]}|                                 |S rY   )
is_symlink)rZ   ps     r   r]   z%_safe_skills_path.<locals>.<listcomp>+  s%    CCCaALLNNCCCCr   *z:credential_files: skipping symlink in skills dir: %s -> %sr   NTignore_errorszhermes-skills-safe-)prefix)parentsexist_okc                 b                                      r                     d           d S d S )NTrw   )rh   rmtree)safe_dirshutils   r   _cleanupz#_safe_skills_path.<locals>._cleanupI  s;    ?? 	8MM($M77777	8 	8r   z8credential_files: created symlink-safe skills copy at %s)rglobr   r)   r*   r&   readlinkatexitr   tempfilerq   rh   r}   r   mkdtemprt   relative_tomkdirr-   parentcopy2registerinfo)rm   symlinkslinkr   r   rR   rS   targetr   r~   r   s            @@r   ri   ri   '  s    DC:++C00CCCH : 0 0SR[..	0 	0 	0 	0 MMMMMMOOO  @ 4 ; ; = = @*$???H$$,A$BBCCH#  %% 	1 	1?? 	z**C;;== 	1LLL5555\\^^ 	1Mt<<<LLTCKK0008 8 8 8 8 8 OOH
KKJHUUUx==r   c                P   g }t                      }|dz  }|                                r|                     d           d}|                    d          D ]j}|                                s|                                s+|                    |          }|                    t          |          | d| d           k	 ddl	m
} t           |                      D ]\  }}	|	                                s|                     d           d| }|	                    d          D ]j}|                                s|                                s+|                    |	          }|                    t          |          | d| d           kn# t          $ r Y nw xY w|S )	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.
    rc   r%   rd   rv   rM   r   re   rg   )r   rh   r2   r   rt   r-   r   rC   r   rj   rf   rk   rl   )
r    rO   r3   rm   container_rootrR   rS   rf   rn   ro   s
             r   iter_skills_filesr   R  s    $&F&((Kx'J 	*11#66???$$S)) 	 	D    "":..CMM YY%3";";c";";     >>>>>>%&>&>&@&@AA 	 	LC>>##  . 5 5c : :RRSRRNc**  ??$$ DLLNN &&w//!$T)7&?&?#&?&?     			     Ms   CF 
F#"F#))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 ]d\  }} |||          }|                                r?|                     d           d| }|                    t          |          |d           e|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%   rM   )r   r   r   rh   r2   rC   r   )r    r   r_   new_subpathold_namehost_dirr8   s          r   get_cache_directory_mountsr     s     0/////#%F!,  X!>+x88?? 	 . 5 5c : :JJ[JJNMM ]]"0     Mr   r4   Optional[str]c                   t          |           }t          |          D ]l}t          |d                   }	 |                    |          }n# t          $ r Y :w xY wt	          j        |d         |                                          c S dS )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    r4   r8   N)r   r   r   
ValueError	posixpathjoinas_posix)r4   r    r'   mountr   rS   s         r   map_cache_path_to_containerr     s     	??D+>JJJ G Gk*++	""8,,CC 	 	 	H	~e$45s||~~FFFFF4s   A
AAr8   c                <   t           j                            dd          dk    r| S t          |           }t	          |          D ]U}	 |                    |d                   }n# t          $ r Y +w xY wt          t          |d                   |z            c S | S )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   r8   r4   )r&   environr   r   r   r   r   r   )r8   r    r'   r   rS   s        r   from_agent_visible_cache_pathr     s     
z~~ng..(::D+>JJJ 3 3	""5)9#:;;CC 	 	 	H	4k*++c122222s   	A%%
A21A2c                |    t           j                            dd          dk    r| S t          | |          }||n| 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   )r4   r    mappeds      r   to_agent_visible_cache_pathr     sF     
z~~ng..(::(>RRRF'66Y6r   c                   ddl m} g }t          D ]\  }} |||          }|                                s&|                     d           d| }|                    d          D ]j}|                                s|                                s+|                    |          }|	                    t          |          | d| d           k|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%   rv   rM   )r   r   r   rh   r2   r   rt   r-   r   rC   r   )	r    r   rO   r   r   r   r   rR   rS   s	            r   iter_cache_filesr     s    0/////#%F!,  X!>+x88   	*11#66FFFFNN3'' 	 	D    ""8,,CMM YY%3";";c";";     		 Mr   Nonec                 F    t                                                       dS )z8Reset the skill-scoped registry (e.g. on session reset).N)r   clearrY   r   r   clear_credential_filesr   	  s     r   )r   r   )r   r   )r   )r   r   r    r   r   r!   )r:   r;   r    r   r   r<   )r   rH   )r    r   r   ra   )rm   r   r   r   )r    r   r   rH   )r4   r   r    r   r   r   )r8   r   r    r   r   r   )r4   r   r    r   r   r   )r   r   )*__doc__
__future__r   loggingr&   r   contextvarsr   pathlibr   typingr   r   r   rN   r	   agent.file_safetyr
   rl   	getLogger__name__r)   r   __annotations__r   r   r   r9   rG   rV   r`   rp   rq   ri   r   r   r   r   r   r   r   r   rY   r   r   <module>r      s    ( # " " " " "  				     " " " " " "       ' ' ' ' ' ' ' ' ' ' % % % % % % 6666666        
	8	$	$ 5?J?R4S4S  S S S S    .2 1 1 1 1    *V V V V Vv *    2* * * *Z   6 ** * * * *Z %)  ( ( ( (( ( ( (X *+ + + + +h& & &     *    4 *    4 *    4 *7 7 7 7 7, *    6     s   : AA