
    jh jYf                        U d Z ddlmZ ddlZddlmZ ddlmZ d'dZd'd	Z	d(dZ
d)dZd*dZd+dZh dZded<   d,dZdZd-dZd.dZd,dZd/d Zd.d!Zd,d"Z	 d0d1d%Z	 d0d2d&ZdS )3z:Shared file safety rules used by both tools and ACP shims.    )annotationsN)Path)Optionalreturnr   c                     	 ddl m}   |             S # t          $ r/ t          t          j                            d                    cY S w xY w)zHResolve the active HERMES_HOME (profile-aware) without circular imports.r   get_hermes_home	~/.hermes)hermes_constantsr	   	Exceptionr   ospath
expanduserr   s    6/home/ubuntu/.hermes/hermes-agent/agent/file_safety.py_hermes_home_pathr   
   sf    5444444    5 5 5BG&&{33444445    6A
Ac                     	 ddl m}   |             S # t          $ r/ t          t          j                            d                    cY S w xY w)zRResolve the Hermes root dir (always the parent of any profile, never per-profile).r   get_default_hermes_rootr
   )r   r   r   r   r   r   r   r   s    r   _hermes_root_pathr      sg    5<<<<<<&&((( 5 5 5BG&&{33444445r   homestrset[str]c                L   t                      }t                      }d t          j                            | dd          t          j                            | dd          t          j                            | dd          t          j                            | dd          t          |dz            t          |dz            t          |dz            t          |dz            t          j                            | d	          t          j                            | d
          t          j                            | d          t          j                            | d          t          j                            | d          t          j                            | d          t          j                            | d          t          j                            | d          t          j                            | d          t          j                            | d          dddfD             S )z8Return exact sensitive paths that must never be written.c                L    h | ]!}t           j                            |          "S  )r   r   realpath.0ps     r   	<setcomp>z+build_write_denied_paths.<locals>.<setcomp>    s8        	      .sshauthorized_keysid_rsa
id_ed25519config.env.anthropic_oauth.jsonz.bashrcz.zshrcz.profilez.bash_profilez	.zprofilez.netrcz.pgpassz.npmrcz.pypircz.git-credentialsz/etc/sudoersz/etc/passwdz/etc/shadow)r   r   r   r   joinr   )r   hermes_homehermes_roots      r   build_write_denied_pathsr-      s   #%%K#%%K  GLLv'899GLLvx00GLLv|44GLLvx00f$%% f$%%5566 5566GLLy))GLLx((GLLz**GLL//GLL{++GLLx((GLLy))GLLx((GLLy))GLL1227
   r"   	list[str]c                   d t           j                            | d          t           j                            | d          t           j                            | d          t           j                            | d          ddt           j                            | d          t           j                            | d	          t           j                            | d
d          t           j                            | d
d          f
D             S )z?Return sensitive directory prefixes that must never be written.c                f    g | ].}t           j                            |          t           j        z   /S r   )r   r   r   sepr   s     r   
<listcomp>z/build_write_denied_prefixes.<locals>.<listcomp>D   s?        	bf$  r"   r#   z.awsz.gnupgz.kubez/etc/sudoers.dz/etc/systemdz.dockerz.azurez.configghgcloud)r   r   r*   )r   s    r   build_write_denied_prefixesr5   B   s      GLLv&&GLLv&&GLLx((GLLw''GLLy))GLLx((GLLy$//GLLy(33
   r"   Optional[str]c                     t          j        dd          } | sdS 	 t           j                            t           j                            |                     S # t
          $ r Y dS w xY w)zBReturn the resolved HERMES_WRITE_SAFE_ROOT path, or None if unset.HERMES_WRITE_SAFE_ROOT N)r   getenvr   r   r   r   )roots    r   get_safe_write_rootr<   U   sn    9-r22D tw 2 24 8 8999   tts   ;A 
A%$A%r   boolc           	        t           j                            t           j                            d                    }t           j                            t           j                            t	          |                               }|t          |          v rdS t          |          D ]}|                    |          r dS d}d}g }t                      t                      fD ]K}	 t           j                            |          }||vr|
                    |           <# t          $ r Y Hw xY w|D ]R}	|D ]X}
	 |t           j                            t           j                            |	|
                    k    r  dS I# t          $ r Y Uw xY w	 t           j                            t           j                            |	|                    }||k    s"|                    |t           j        z             r dS n# t          $ r Y nw xY w	 t           j                            t           j                            |	d                    }||k    s"|                    |t           j        z             r dS C# t          $ r Y Pw xY wt                      }|r*||k    s$|                    |t           j        z             sdS dS )zBReturn True if path is blocked by the write denylist or safe root.~T)	auth.jsonzconfig.yamlwebhook_subscriptions.json
mcp-tokenspairingF)r   r   r   r   r   r-   r5   
startswithr   r   appendr   r*   r1   r<   )r   r   resolvedprefixcontrol_file_namesmcp_tokens_dir_namehermes_dirsbasereal	base_realnamemcp_realpairing_real	safe_roots                 r   is_write_deniedrR   `   s   7BG..s3344Dw 2 23t99 = =>>H+D1111t-d33  v&& 	44	 T&K"$$&7&9&9:  	7##D))D;&&""4((( 	 	 	H	 !  	& 	 	Drw//Y0M0MNNNN444 O   	w''Y@S(T(TUUH8##x':':8bf;L'M'M#tt $ 	 	 	D		7++BGLLI,N,NOOL<''8+>+>|bf?T+U+U'tt ( 	 	 	D	 $%%I (i//83F3FySUSYGY3Z3Z/t5sK   (8D!!
D.-D.;AF
FFA%G;;
HHA%I66
JJ>   .envrc	.env.test
.env.local.env.staging.env.production.env.developmentr(   _BLOCKED_PROJECT_ENV_BASENAMESc           
        t          |                                                                           }g }t                      t	                      fD ]@}	 |                                }||vr|                    |           1# t          $ r Y =w xY w|D ]J}|dz  dz  dz  |dz  dz  g}|D ]2}	 |                    |           n# t          $ r Y %w xY wd|  dc c S Kdddd	d
t          j
                            dd          t          j
                            dd          f}|D ]@}|D ];}		 ||	z                                  }n# t          $ r Y 'w xY w||k    r
d|  dc c S <A|D ]g}	 |dz                                  }
n# t          $ r Y 'w xY w||
k    rd|  dc S 	 |                    |
           n# t          $ r Y \w xY wd|  dc S |j        t          v rd|  dS dS )u	  Return an error message when a read targets a denied Hermes path.

    Three categories are blocked:

      * Internal Hermes cache files under ``HERMES_HOME/skills/.hub`` —
        readable metadata that an attacker could use as a prompt-injection
        carrier.
      * Credential / secret stores under HERMES_HOME and the global Hermes
        root: ``auth.json``, ``auth.lock``, ``.anthropic_oauth.json``,
        ``.env``, ``webhook_subscriptions.json``, ``auth/google_oauth.json``,
        and anything under ``mcp-tokens/``. These hold plaintext provider keys,
        OAuth tokens, and HMAC secrets that the agent never needs to read
        directly — provider tools / gateway adapters consume them through
        internal channels.
      * Project-local environment files anywhere on disk: ``.env``,
        ``.env.local``, ``.env.development``, ``.env.production``,
        ``.env.test``, ``.env.staging``, ``.envrc``. These routinely hold
        API keys, database passwords, and other credentials for the user's
        own projects. The agent helping debug a project shouldn't normally
        need to read these — ``.env.example`` is the documented-shape
        substitute.

    **This is NOT a security boundary.** The terminal tool runs as the
    same OS user with shell access; the agent can still ``cat auth.json``
    or ``cat ~/.hermes/.env`` and exfiltrate the file. The read-deny exists
    as defense-in-depth that:

      * Returns a clear error to models that respect tool denials, which
        empirically prompts most modern models to stop rather than reach
        for the shell.
      * Surfaces a visible audit trail when something tries to read
        credentials — easier to spot in logs than a generic ``cat``.

    Treat any user-visible framing around this as "may help" rather than
    "stops attackers." A determined model or malicious instruction can
    always shell out.

    Callers that resolve relative paths against a non-process cwd
    (e.g. ``TERMINAL_CWD`` in ``tools/file_tools.py``) MUST pre-resolve
    and pass the absolute path string.  This function's own ``resolve()``
    is anchored at the Python process cwd, so a relative input like
    ``"auth.json"`` would otherwise miss the denylist when the task's
    terminal cwd differs from the process cwd.
    skillsz.hubzindex-cachezAccess denied: z is an internal Hermes cache file and cannot be read directly to prevent prompt injection. Use the skills_list or skill_view tools instead.r@   z	auth.lockr)   r(   rA   authzgoogle_oauth.jsoncachezbws_cache.jsonu    is a Hermes credential store and cannot be read directly. Provider tools consume these credentials through internal channels. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.)rB   u    is the Hermes MCP token directory and cannot be read directly. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.)u    is a Hermes MCP token file and cannot be read directly. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.)u    is a secret-bearing environment file and cannot be read to prevent credential leakage. If you need to check the file structure, read .env.example instead. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.)N)r   r   resolver   r   rE   r   relative_to
ValueErrorr   r   r*   rN   rY   )r   rF   rJ   rK   rL   hdblocked_dirsblockedcredential_file_namesrN   
mcp_tokenss              r   get_read_block_errorrf      s\   Z Dzz$$&&..00H !K"$$&7&9&9:  	<<>>D;&&""4((( 	 	 	H	   MF"]2MF"
 $ 		 		G$$W----   C$ C C C    		 	$
V011 	W.//   ) 	 	D9--//   7""7d 7 7 7     #	   
 
	|+4466JJ 	 	 	H	z!!J$ J J J  
	  ,,,, 	 	 	H	Fd F F F	
 	
 	
 }666bd b b b	
 4sZ   -B
BB/C
CC.E
EE-F
FF$F::
GG)r[   pluginscronmemoriesc                 V   	 t                                                      } t                                                      }n# t          t          f$ r Y dS w xY w|dz  }	 |                     |          }|j        }t          |          dk    r|d         S n# t          $ r Y nw xY wdS )a  Return the active profile name derived from HERMES_HOME.

    ``~/.hermes``              -> ``"default"``
    ``~/.hermes/profiles/X``  -> ``"X"``

    Falls back to ``"default"`` on any resolution failure so the guard
    never raises into the tool path.
    defaultprofiles   r   )	r   r^   r   OSErrorRuntimeErrorr_   partslenr`   )	home_real	root_realprofiles_dirrelrp   s        r   _resolve_active_profile_namerv   S  s    %''//11	%''//11		\"   yyz)L##L11	u::??8O    9s$   A A AA!6B 
B&%B&Optional[dict]c                   	 t          t          j                            t	          |                                                               }t                                                      }n# t          t          f$ r Y dS w xY wd}d}	 |	                    |          }n# t          $ r Y dS w xY w|j        }|sdS |d         t          v rd}|d         }nA|d         dk    r3t          |          dk    r |d         t          v r|d         }|d         }ndS t                      }||k    rdS |||t	          |          dS )	u  Classify a write target as cross-profile if it lands in another
    profile's scoped area (skills/plugins/cron/memories).

    Returns ``None`` when the target is outside Hermes scope, or is inside
    the ACTIVE profile, or doesn't hit a profile-scoped area. Otherwise
    returns a dict with:

      * ``active_profile``: name of the profile the agent is running as
      * ``target_profile``: name of the profile the path belongs to
      * ``area``: which scoped area (``"skills"``, ``"plugins"``, etc.)
      * ``target_path``: the resolved path string

    The caller decides what to do with the result — surface a warning to
    the model, prompt the user, or (with explicit consent /
    ``cross_profile=True``) proceed anyway.
    Nr   rk   rl         rm   )active_profiletarget_profileareatarget_path)r   r   r   r   r   r^   r   rn   ro   r_   r`   rp   PROFILE_SCOPED_AREASrq   rv   )r   targetrs   r|   r}   ru   rp   r{   s           r   classify_cross_profile_targetr   l  s{   "bg((T3344<<>>%''//11		\"   tt %)ND  ++   tt IE tQx'''"QxaJJJ!OO!H,,, qQxt133N''t )(6{{	  s$   A+A. .BBB! !
B/.B/c           	     v    t          |           }|dS d|d          d|d         d|d         d|d	          d
	S )un  Return a model-facing warning string when ``path`` is cross-profile.

    Returns ``None`` when the write is in-scope (same profile) or outside
    Hermes entirely. Caller is expected to surface the warning to the
    agent as a tool-result error, NOT to silently allow the write — the
    agent must either get explicit user direction to proceed, or pass
    ``cross_profile=True`` to its write tool.

    This is defense-in-depth: the terminal tool runs as the same OS user
    and can write any of these paths without going through this guard.
    Treat the guard as a confusion-reducer, not a security boundary.
    Nz+Cross-profile write blocked by soft guard: r~   z belongs to Hermes profile r|   z), but the agent is running under profile r{   z. Editing another profile's r}   u,  / will affect that profile's future sessions, not the one you are currently in. Confirm with the user before proceeding. To bypass this guard after explicit user direction, retry the call with ``cross_profile=True``. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.))r   r   infos     r   get_cross_profile_warningr     st     )..D|t	:d=6I 	: 	:%)*:%;	: 	:*./?*@	: 	: &*&\	: 	: 	:
r"   rp   tupleOptional[int]c                    t          |           D ]H\  }}|dk    r|dz   t          |           k    r#| |dz            dk    r| |dz            dk    r|dz   c S IdS )u"  Return the index of the inner ``.hermes`` part in a sandbox-mirror path.

    Matches ``…/sandboxes/<backend>/<task>/home/.hermes/…`` and returns the
    index where the inner Hermes-state portion starts. Returns ``None`` for
    paths that do not contain the sandbox-mirror shape.
    	sandboxes   ry   r      z.hermesN)	enumeraterq   )rp   iparts      r   _find_sandbox_mirror_segmentsr     s}     U##  4;q5CJJQ<6!!eAEli&?&?q5LLL4r"   c                   	 t          t          j                            t	          |                                                               }n# t          t          f$ r Y dS w xY w|j        }t          |          }|dS t	          t          |d|dz                       }|dz   t          |          k     r!t	          t          ||dz   d                    nd}t	          |          ||dS )u  Classify a write target as a sandbox-mirror of authoritative Hermes state.

    Returns ``None`` when the path does not match the sandbox-mirror shape.
    Otherwise returns a dict with:

      * ``target_path``: the resolved path string
      * ``mirror_root``: the ``…/sandboxes/<backend>/<task>/home/.hermes``
        prefix (so callers can show users which sandbox owns the mirror)
      * ``inner_path``: the portion under the mirror's ``.hermes`` (what the
        agent likely meant to address on the host)

    Detection is path-shape-only — does not require any Hermes resolver to
    succeed, so it works correctly even when called from contexts where
    HERMES_HOME resolution would be ambiguous.
    Nrm   r9   r~   mirror_root
inner_path)r   r   r   r   r   r^   rn   ro   rp   r   rq   )r   r   rp   	inner_idxr   r   s         r   classify_sandbox_mirror_targetr     s     bg((T3344<<>>\"   tt LE-e44ItdE/IM/2344K7@1}s5zz7Q7QT5Q12333WYJ 6{{"   s   AA A#"A#c                d    t          |           }|dS d|d          d|d         d|d         dS )	a  Return a model-facing warning when ``path`` lands in a sandbox mirror.

    Returns ``None`` when the path is not a sandbox-mirror target. Caller
    is expected to surface the warning to the agent as a tool-result
    error. The bypass kwarg (``cross_profile=True``) is shared with the
    cross-profile guard: both are soft "I know what I'm doing" overrides
    a user can authorise.

    Defense-in-depth, NOT a security boundary: the terminal tool runs as
    the same OS user and can write the mirror path directly. The guard
    exists to surface the misclassification before the silent-success +
    divergent-copy footgun in #32049 fires.
    N,Sandbox-mirror write blocked by soft guard: r~    sits under r   u   , which is a per-task mirror created by a non-local terminal backend (docker/daytona/etc.). Writes here land on a copy that the host Hermes process never reads — the authoritative file is likely r   uB   under the real HERMES_HOME. Use the host-side tool for authoritative state (e.g. ``memory`` for memories), or address the host path directly. To bypass this guard after explicit user direction, retry the call with ``cross_profile=True``. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.))r   r   s     r   get_sandbox_mirror_warningr     sa     *$//D|t
	tM7J 
	 
	=)
	 
	 7;<6H	
	 
	 
	r"   mirror_prefix
str | Nonec                   |sdS 	 t          t          j                            t	          |                                                               }t          t          j                            |                                                    }|                    |          }n# t          t          t          f$ r Y dS w xY wt	          |          t	          |          |
                                dS )aC  Classify a write target as a container-side sandbox mirror.

    ``mirror_prefix`` must be supplied by the caller after it has established
    that file tools are executing in a container whose home is a sandbox
    mirror. Returns ``None`` when no such context is active or the path is not
    under the mirror prefix. Otherwise returns:

      * ``target_path``: resolved path string
      * ``mirror_root``: the declared container mirror prefix
      * ``inner_path``: portion under the mirror root (what the agent
        likely meant to address in the host HERMES_HOME)
    Nr   )r   r   r   r   r   r^   r_   rn   ro   r`   as_posix)r   r   r   mirrorinners        r    classify_container_mirror_targetr   F  s       tbg((T3344<<>>bg((7788@@BB""6**\:.   tt 6{{6{{nn&&  s   BB% %C ?C c                f    t          | |          }|dS d|d          d|d         d|d         dS )	a  Return a model-facing warning when *path* lands in the container's
    sandbox mirror of authoritative Hermes state.

    The caller supplies ``mirror_prefix`` only when the current file-tool
    backend is known to execute inside a Docker sandbox. Same contract as
    ``get_cross_profile_warning``: soft guard, returns ``None`` for
    non-mirror paths, caller surfaces as a tool-result error. Bypass via
    ``cross_profile=True`` after explicit user direction.
    Nr   r~   r   r   u   , which is the container's bind-mounted home — a per-task mirror that the host Hermes process never reads. The authoritative file is r   u.   under the real HERMES_HOME. Use the host-side tool for authoritative state (e.g. ``memory`` for memories), or address the host path directly. To bypass after explicit user direction, retry with ``cross_profile=True``. (Defense-in-depth — not a security boundary; the terminal tool can still bypass.))r   )r   r   r   s      r   get_container_mirror_warningr   e  sc     ,D-@@D|t		tM7J 		 		=)		 		 			 		 		r"   )r   r   )r   r   r   r   )r   r   r   r.   )r   r6   )r   r   r   r=   )r   r   r   r6   )r   r   )r   r   r   rw   )rp   r   r   r   )N)r   r   r   r   r   rw   )r   r   r   r   r   r6   )__doc__
__future__r   r   pathlibr   typingr   r   r   r-   r5   r<   rR   rY   __annotations__rf   r   rv   r   r   r   r   r   r   r   r   r"   r   <module>r      s   @ @ @ " " " " " " 				            5 5 5 55 5 5 5# # # #L   &   4 4 4 4t, , ,     O O O OV A    2< < < <~   j   $! ! ! !H   d !%    B !%      r"   