Ë
    áß²jh  ã            
      óä  — U d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	 d9d„Z
d:d„Zd:d	„Zd;d
„Zd;d„Zd<d„Zd=d„Zd>d?d„Zd@d„ZdAd„ZddœdBd„ZdCd„ZdDd„ZdEd„ZdCd„ZdZdFd„ZdAd„ZddœdBd„ZdAd„Zh d£Zded <   d!Z d"d#d$d%d&ejB                  jE                  d'd(«      ejB                  jE                  d)d*«      fZ#d+Z$dFd,„Z%dGd-„Z&dHd.„Z'd/Z(dId0„Z)dJd1„Z*dKd2„Z+dFd3„Z,dLdMd4„Z-dLdNd5„Z.d6Z/dJd7„Z0dFd8„Z1y)Oa  Shared file safety rules used by both tools and ACP shims.

Every guard here is defense-in-depth, NOT a security boundary: the terminal
tool runs as the same OS user and can read/write anything. The value is a
clear denial for models that respect tool errors plus a visible audit trail.
é    )ÚannotationsN)ÚPath)Úsuppress)ÚOptionalc                óœ   — 	 ddl } t        || «      «       S # t        $ r+ t        t        j
                  j                  d«      «      cY S w xY w)zeCall ``hermes_constants.<getter_name>()`` (local import avoids cycles); ``~/.hermes`` on any failure.r   Nz	~/.hermes)Úhermes_constantsÚgetattrÚ	Exceptionr   ÚosÚpathÚ
expanduser)Úgetter_namer   s     ú6/home/ubuntu/.hermes/hermes-agent/agent/file_safety.pyÚ_constants_pathr      sF   € ð5Ûà5ŒwÐ'¨Ó5Ó7Ð7øÜò 5Ü”B—G‘G×&Ñ& {Ó3Ó4Ò4ð5ús   ‚ —1AÁ
Ac                 ó   — t        d«      S )z@Active HERMES_HOME (profile-aware). Tests monkeypatch this name.Úget_hermes_home©r   © ó    r   Ú_hermes_home_pathr      s   € äÐ,Ó-Ð-r   c                 ó   — t        d«      S )z;Hermes root dir (parent of any profile, never per-profile).Úget_default_hermes_rootr   r   r   r   Ú_hermes_root_pathr      s   € äÐ4Ó5Ð5r   c            	     ót   — t        t        j                  t        t	        «       t        «       f«      «      «      S )zÎResolved active HERMES_HOME and global root, deduplicated.

    Both are checked so credential stores at <root>/... stay guarded when
    running under a profile (HERMES_HOME = <root>/profiles/<name>).
    )ÚlistÚdictÚfromkeysÚ_resolve_eachr   r   r   r   r   Ú_hermes_dirsr   $   s*   € ô ”—‘œmÔ->Ó-@ÔBSÓBUÐ,VÓWÓXÓYÐYr   c                óž   — g }| D ]9  }t        t        «      5  |j                  |j                  «       «       ddd«       Œ; |S # 1 sw Y   ŒGxY w)zB``p.resolve()`` for each path, skipping ones that fail to resolve.N)r   r
   ÚappendÚresolve)ÚpathsÚoutÚps      r   r   r   -   sQ   € à€CØò $ˆÜ”iÓ ñ 	$Ø�J‰J�q—y‘y“{Ô#÷	$ð 	$ð$ð €J÷	$ð 	$ús   — AÁA	c                óÌ   — t        | t        «      r	 | j                  |«      duS | |k(  xs+ | j	                  t        |«      t        j                  z   «      S # t        $ r Y yw xY w)z®True when ``resolved`` equals ``base`` or lies below it (both already resolved);
    ``Path`` inputs use ``relative_to`` (platform semantics), ``str`` a realpath prefix test.NF)Ú
isinstancer   Úrelative_toÚ
ValueErrorÚ
startswithÚstrr   Úsep)ÚresolvedÚbases     r   Ú	_is_underr/   6   sg   € ô �(œDÔ!ð	Ø×'Ñ'¨Ó-°TÐ9Ð9ð �tÑÒF˜x×2Ñ2´3°t³9¼r¿v¹vÑ3EÓFÐFøô ò 	Ùð	ús   ’A Á	A#Á"A#c                óÔ   — t        t        t        «      5  t        t        j
                  j                  t        | «      «      «      j                  «       cddd«       S # 1 sw Y   yxY w)zD``Path(expanduser(path)).resolve()``, or None when resolution fails.N)	r   ÚOSErrorÚRuntimeErrorr   r   r   r   r+   r"   ©r   s    r   Ú_resolve_targetr4   A   sH   € ä	”'œ<Ó	(ñ =Ü”B—G‘G×&Ñ&¤s¨4£yÓ1Ó2×:Ñ:Ó<÷=÷ =ñ =àús   •?AÁA'úset[str]c                ó°  — t         j                  j                  d«      h}t        t        «      5  ddlm}m}m}  |«        |«        |«       fD ]  }|sŒ|j                  t        |«      «       Œ! 	 ddd«       t        | «      }t        |«      dkD  r‡|j                  d«      rv|d   dvro|dd j                  dd«      d   j                  dd«      d   }t         j                  j                  d|› �«      }|j                  d«      s|j                  |«       |D �	ch c]!  }	t         j                  j                  |	«      ’Œ# c}	S # 1 sw Y   ŒÖxY wc c}	w )	u¨  Every home the write guards must cover. Process ``~`` alone is wrong whenever the
    process HOME is not the OS user's real home â€” ``TERMINAL_HOME_MODE=profile``,
    containers, and spawned workers pin ``HOME`` to ``{HERMES_HOME}/home``, which leaves
    the real home's credential paths unguarded against absolute-path writes while file
    tools happily write there (and ``_expand_tilde`` may route ``~`` to yet another
    home). Deny/approval lists are built over the union: process home, real home,
    subprocess home, and the profile home. A ``~name/...`` input resolves to a named
    account's home, which joins the set so ``~root/.ssh/authorized_keys`` stays denied.Ú~r   )Úget_real_homeÚget_subprocess_homeÚ_profile_home_pathNé   z/\ú/ú\)r   r   r   r   r
   r   r8   r9   r:   Úaddr+   Úlenr*   ÚsplitÚrealpath)
r   Úhomesr8   r9   r:   Ú	candidateÚrawÚnameÚexpandedÚhs
             r   Ú_guard_homesrH   H   s"  € ô �W‰W×Ñ Ó$Ð%€EÜ	”)Ó	ñ *ß[Ñ[á'›/Ñ+>Ó+@ÑBTÓBVÐWò 	*ˆIÚØ—	‘	œ#˜i›.Õ)ñ	*÷*ô ˆd‹)€CÜ
ˆ3ƒx�!‚|˜Ÿ™ sÔ+°°A±¸eÑ0CØ�1�2ˆw�}‰}˜S !Ó$ QÑ'×-Ñ-¨d°AÓ6°qÑ9ˆÜ—7‘7×%Ñ%¨¨$¨ jÓ1ˆØ×"Ñ" 3Ô'Ø�I‰I�hÔØ).Ö/ AŒB�G‰G×Ñ˜QÕÒ/Ð/÷*ð *üò 0s   °$EÁEÄ&EÅEc                ó¢   — t        | «      t        j                  j                  t        j                  j	                  t        | «      «      «      fS )uR   ``(guard homes, realpath(expanduser(path)))`` â€” the write-guard coordinate pair.)rH   r   r   rA   r   r+   r3   s    r   Ú_homes_and_resolvedrJ   a   s4   € ä˜ÓœrŸw™w×/Ñ/´·±×0BÑ0BÄ3ÀtÃ9Ó0MÓNÐNÐNr   c                ó  — t        | «      j                  dd«      }|j                  d«      ry|j                  d«      ry|j                  d«      r8|dd }|j                  «       }|j                  d	«      s|j                  d
«      ryy)uÍ   Return True if ``path`` is a Windows NT-/device-namespace path.

    Checks the raw string only â€” never resolves the path (resolving is the
    credential-leak trigger this guard exists to prevent).
    r<   r=   z\??\Tz\\.\z\\?\é   NzUNC\zGLOBALROOT\F)r+   Úreplacer*   Úupper)r   ÚsÚrestrN   s       r   Úis_nt_namespace_pathrQ   �   s{   € ô 	ˆD‹	×Ñ˜#˜tÓ$€AØ‡|�|�HÔØØ‡|�|�IÔØØ‡|�|�IÔØ��ˆuˆØ—
‘
“ˆØ×Ñ˜GÔ$¨×(8Ñ(8¸Ô(HØØr   ÚAccess©Úverbc               ó*   — t        | «      sy|› d| › d�S )zCReturn an error message when ``path`` uses the NT/device namespace.Nú
 denied: 'a   ' uses a Windows NT/device namespace prefix (\??\, \\.\, \\?\UNC\, or GLOBALROOT). These paths bypass normal path normalization and can trigger outbound SMB authentication (NTLM credential leak) merely by being resolved. Use a normal absolute path instead.)rQ   )r   rT   s     r   Úget_nt_namespace_errorrW   £   s'   € ä Ô%Øàˆ&�
˜4˜&ð !.ð 	.ðr   c                óf  ‡ — d}ddt         j                  j                  dd«      t         j                  j                  dd«      t         j                  j                  dd«      f}g ˆ fd	„|D «       ¢d
„ |D «       ¢d‘d‘d‘}|D �ch c]!  }t         j                  j                  |«      ’Œ# c}S c c}w )z8Return exact sensitive paths that must never be written.))ú.sshÚauthorized_keys)rY   Úid_rsa)rY   Ú
id_ed25519)z.netrc)z.pgpass)z.npmrc)z.pypirc)z.git-credentialsú.envú.anthropic_oauth.jsonÚauthúgoogle_oauth.jsonÚcacheúbws_cache.jsonzbws_cache.enc.jsonc              3  ó^   •K  — | ]$  }t        j                  j                  ‰g|¢­Ž –— Œ& y ­w©N©r   r   Újoin)Ú.0ÚfÚhomes     €r   ú	<genexpr>z+build_write_denied_paths.<locals>.<genexpr>Ê   s#   øè ø€ Ò	5 QŒ"�'‰'�,‰,�tÐ
 ˜aÖ
 Ñ	5ùó   ƒ*-c              3  ój   K  — | ]+  }t        «       t        «       fD ]  }t        ||z  «      –— Œ Œ- y ­wrd   )r   r   r+   )rg   rh   r.   s      r   rj   z+build_write_denied_paths.<locals>.<genexpr>Ë   s1   è ø€ Ò	e˜AÔ;LÓ;NÔPaÓPcÐ:dÒ	e°$Œ#ˆd�Q‰h�-Ð	eˆ-Ñ	eùs   ‚13z/etc/sudoersz/etc/passwdz/etc/shadow)r   r   rf   rA   )ri   Ú
home_filesÚhermes_filesr#   r%   s   `    r   Úbuild_write_denied_pathsro   °   s¶   ø€ ð
€Jð 	Ð'Ü
�‰�‰�VÐ0Ó1Ü
�‰�‰�WÐ.Ó/Ü
�‰�‰�WÐ2Ó3ð	€LðÛ	5¨*Ô	5ðá	e Ô	eðð 	ðð &ðð (5ð€Eð
 */Ö/ AŒB�G‰G×Ñ˜QÕÒ/Ð/ùÒ/s   Â&B.c                óÂ   ‡ — g ˆ fd„dD «       ¢d‘d‘ˆ fd„dD «       ¢}|D �cg c]2  }t         j                  j                  |«      t         j                  z   ‘Œ4 c}S c c}w )z?Return sensitive directory prefixes that must never be written.c              3  ó^   •K  — | ]$  }t         j                  j                  ‰|«      –— Œ& y ­wrd   re   ©rg   Údri   s     €r   rj   z.build_write_denied_prefixes.<locals>.<genexpr>Ô   s    øè ø€ Ò	M AŒ"�'‰'�,‰,�t˜Q×
Ñ	Mùrk   )rY   z.awsz.gnupgz.kubez/etc/sudoers.dz/etc/systemdc              3  ó^   •K  — | ]$  }t        j                  j                  ‰g|¢­Ž –— Œ& y ­wrd   re   rr   s     €r   rj   z.build_write_denied_prefixes.<locals>.<genexpr>Ö   s#   øè ø€ Ò	p QŒ"�'‰'�,‰,�tÐ
 ˜aÖ
 Ñ	pùrk   ))z.docker)z.azure)ú.configÚgh)ru   Úgcloud)r   r   rA   r,   )ri   r#   r%   s   `  r   Úbuild_write_denied_prefixesrx   Ñ   sg   ø€ ðÛ	MÐ)LÔ	Mðàðà(ðó 
qÐ*oÔ	pð€Eð
 38Ö8¨QŒB�G‰G×Ñ˜QÓ¤"§&¡&Ó(Ò8Ð8ùÒ8s   ¢7Ac            	     ó†  — t        «       } t        dt        j                  dd«      j	                  t        j
                  «      «      D ]j  }t        t        t        «      5  | j                  t        j                  j                  t        j                  j                  |«      «      «       ddd«       Œl | S # 1 sw Y   ŒxxY w)zFResolved HERMES_WRITE_SAFE_ROOT paths (``os.pathsep``-separated list).NÚHERMES_WRITE_SAFE_ROOTÚ )ÚsetÚfilterr   Úgetenvr@   Úpathsepr   r1   r)   r>   r   rA   r   )Úrootsr   s     r   Úget_safe_write_rootsr�   Û   s–   € ä“e€EÜ�tœRŸY™YÐ'?ÀÓD×JÑJÌ2Ï:É:ÓVÓWò BˆÜ”gœzÓ*ñ 	BØ�I‰I”b—g‘g×&Ñ&¤r§w¡w×'9Ñ'9¸$Ó'?Ó@ÔA÷	Bð 	BðBð €L÷	Bð 	Bús   ÁAB7Â7C 	c                ó€   — t         j                  j                  t         j                  j                  | dd«      «      hS )ay  Paths that need human APPROVAL to write but are not hard-denied credentials.

    ``~/.ssh/config`` is routine to edit and holds no key bytes, but can carry
    ``ProxyCommand`` / ``Match exec``. Interactive file tools prompt
    (approve-once/session/always, like the terminal tool's ``~/.ssh`` gate);
    non-interactive callers (ACP shims, background jobs) fail closed.
    rY   Úconfig)r   r   rA   rf   )ri   s    r   Úbuild_write_approval_pathsr„   ä   s-   € ô �G‰G×ÑœRŸW™WŸ\™\¨$°¸ÓAÓBÐCÐCr   )zstate.dbÚsessionsú
mcp-tokensÚpairingÚvaultúbrowser-profilec                ó  ‡— t        | «      ryt        | «      \  }Št        ˆfd„|D «       «      ryt        ˆfd„|D «       «      ryt        «       D ]‚  }t        D ]w  }t        t        «      5  t        ‰t        j                  j                  t        j                  j                  t        |«      |«      «      «      r	 ddd«         y	 ddd«       Œy Œ„ t        «       }|rt        ˆfd„|D «       «      syy# 1 sw Y   Œ§xY w)z`Return ``'credential'``, ``'safe_root'``, ``'nt_namespace'``, or ``None`` if writes are allowed.Únt_namespacec              3  ó8   •K  — | ]  }‰t        |«      v –— Œ y ­wrd   ©r„   ©rg   ri   r-   s     €r   rj   z)_classify_write_denial.<locals>.<genexpr>  s   øè ø€ Ò
J¸Dˆ8Ô1°$Ó7Ô7Ñ
Jùó   ƒNc              3  óv   •K  — | ]0  }‰t        |«      v xs t        ˆfd „t        |«      D «       «      –— Œ2 y­w)c              3  ó@   •K  — | ]  }‰j                  |«      –— Œ y ­wrd   )r*   )rg   Úprefixr-   s     €r   rj   z3_classify_write_denial.<locals>.<genexpr>.<genexpr>	  s   øè ø€ Ò[¨vˆx×"Ñ" 6×*Ñ[ùs   ƒN)ro   Úanyrx   rŽ   s     €r   rj   z)_classify_write_denial.<locals>.<genexpr>  sG   øè ø€ ò ð ð 	Ô,¨TÓ2Ð2ò 	\ÜÓ[Ô9TÐUYÓ9ZÔ[Ó[ó	\ñùs   ƒ69Ú
credentialc              3  ó6   •K  — | ]  }t        ‰|«      –— Œ y ­wrd   ©r/   )rg   Úrootr-   s     €r   rj   z)_classify_write_denial.<locals>.<genexpr>  s   øè ø€ ÒO¸Dœi¨°$×7ÑOùs   ƒÚ	safe_root)rQ   rJ   r“   r   Ú_HERMES_PROTECTED_SUBPATHSr   r
   r/   r   r   rA   rf   r+   r�   )r   rB   r.   ÚsubÚ
safe_rootsr-   s        @r   Ú_classify_write_denialrœ   ù   sì   ø€ ô
 ˜DÔ!ØÜ)¨$Ó/�O€Eˆ8ô Ó
JÀEÔ
JÔJØä
ó ð ôô ð
 ä“ò (ˆÜ-ò 	(ˆCÜœ)Ó$ñ (Ü˜X¤r§w¡w×'7Ñ'7¼¿¹¿¹ÄSÈÃYÐPSÓ8TÓ'UÔVØ'÷(ò (ØV÷(ð (ñ	(ð(ô &Ó'€JÙœ#ÓOÀJÔOÔOØà÷(ð (ús   Á+AC7Ã7D c                ó   — t        | «      duS )zBReturn True if path is blocked by the write denylist or safe root.N)rœ   r3   s    r   Úis_write_deniedrž     s   € ä! $Ó'¨tÐ3Ð3r   ÚWritec               óÔ   — t        | «      }|dk(  r;t        j                  j                  t	        t        «       «      «      }|› d| › d|› d�S |dk(  rt        | |¬«      S |r|› d| › d�S dS )	zEReturn a user/model-facing error when writes to ``path`` are blocked.r˜   rV   z%' is outside HERMES_WRITE_SAFE_ROOT (z:). Unset the variable or add this path's directory prefix.r‹   rS   z(' is a protected system/credential file.N)rœ   r   r   rf   Úsortedr�   rW   )r   rT   ÚdenialÚroots_displays       r   Úget_write_denied_errorr¤      s…   € ä# DÓ)€FØ�ÒÜŸ
™
Ÿ™¬Ô/CÓ/EÓ(FÓGˆàˆf�J˜t˜fð %ØˆÐXðZð	
ð �ÒÜ% d°Ô6Ð6ÙPVˆdˆV�:˜d˜VÐ#KÐLÐ`Ð\`Ð`r   c                óH   ‡— t        | «      \  }Št        ˆfd„|D «       «      S )z”True if ``path`` is approval-gated (``~/.ssh/config``): interactive callers
    prompt, callers without a channel treat it as a block (fail closed).c              3  ó8   •K  — | ]  }‰t        |«      v –— Œ y ­wrd   r�   rŽ   s     €r   rj   z-is_write_approval_required.<locals>.<genexpr>2  s   øè ø€ ÒNÀˆxÔ5°dÓ;Ô;ÑNùr�   )rJ   r“   )r   rB   r-   s     @r   Úis_write_approval_requiredr§   .  s$   ø€ ô *¨$Ó/�O€Eˆ8ÜÓNÈÔNÓNÐNr   >   ú.envrcú	.env.testú
.env.localú.env.stagingú.env.productionú.env.developmentr]   Ú_BLOCKED_PROJECT_ENV_BASENAMESuT    (Defense-in-depth â€” not a security boundary; the terminal tool can still bypass.)z	auth.jsonz	auth.lockr^   r]   zwebhook_subscriptions.jsonr_   r`   ra   rb   ))r†   z>is the Hermes MCP token directory and cannot be read directly.z7is a Hermes MCP token file and cannot be read directly.)r‰   zjis the Hermes real-profile browser snapshot directory (copied cookies/logins) and cannot be read directly.zgis inside the Hermes real-profile browser snapshot (copied cookies/logins) and cannot be read directly.)rˆ   z|is the Hermes credential vault directory and cannot be read directly (secrets are filled server-side by browser_vault_fill).z‘is inside the Hermes credential vault (encrypted secrets + local key) and cannot be read directly (browser_vault_fill resolves them server-side).c                ó  ‡‡‡— t        | d¬«      }|r|S t        | «      j                  «       j                  «       Št	        «       Šd}t        ˆfd„‰D «       «      rd}n˜t        ˆˆfd„t        D «       «      r
dt        z   }nut        D ]A  \  Š}}t        ˆfd„‰D «       «      D ]   }t        ‰|«      sŒ‰|k(  r|n|t        z   } n |sŒA n |€)‰j                  j                  «       t        v r	d	t        z   }|rd
| › d|› �S dS )aj  Return an error message when a read targets a denied Hermes path.

    Blocked: internal skill-hub caches (prompt-injection carriers), credential
    stores under HERMES_HOME and the global root (exact files, plus anything
    under ``mcp-tokens/`` and ``browser-profile/``), and project-local ``.env``
    files anywhere on disk (``.env.example`` is the documented-shape substitute).

    Callers that resolve relative paths against a non-process cwd (e.g.
    ``TERMINAL_CWD``) MUST pass an absolute path: ``resolve()`` here anchors at
    the process cwd, so a relative ``"auth.json"`` would miss the denylist.
    ÚReadrS   Nc              3  óB   •K  — | ]  }t        ‰|d z  dz  «      –— Œ y­w)Úskillsz.hubNr–   )rg   Úhdr-   s     €r   rj   z'get_read_block_error.<locals>.<genexpr>n  s!   øè ø€ Ò
M¸2Œ9�X˜r H™}¨vÑ5×6Ñ
Mùs   ƒ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.c              3  óL   •‡K  — | ]  Š‰t        ˆfd „‰D «       «      v –— Œ y­w)c              3  ó(   •K  — | ]	  }|‰z  –— Œ y ­wrd   r   )rg   r³   rE   s     €r   rj   z1get_read_block_error.<locals>.<genexpr>.<genexpr>s  s   øè ø€ Ò&G°R r¨D¥yÑ&Gùó   ƒN)r   )rg   rE   Úhermes_dirsr-   s    @€€r   rj   z'get_read_block_error.<locals>.<genexpr>s  s!   ùè ø€ ÒkÈDˆXœÓ&G¸;Ô&GÓGÔGÑkùs   „ $z}is a Hermes credential store and cannot be read directly. Provider tools consume these credentials through internal channels.c              3  ó(   •K  — | ]	  }|‰z  –— Œ y ­wrd   r   )rg   r³   Úsubdirs     €r   rj   z'get_read_block_error.<locals>.<genexpr>z  s   øè ø€ Ò,O¸R¨R°&­[Ñ,Oùr¶   zš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.zAccess denied: ú )rW   r   r   r"   r   r“   Ú_CREDENTIAL_FILE_NAMESÚ_DID_SUFFIXÚ_READ_DENIED_DIRSr   r/   rE   Úlowerr®   )	r   Únt_errorÚreasonÚdir_msgÚfile_msgÚblocked_dirr·   r-   r¹   s	         @@@r   Úget_read_block_errorrÄ   W  s%  ú€ ô" & d°Ô8€HÙØˆÜ�D‹z×$Ñ$Ó&×.Ñ.Ó0€HÜ“.€KØ€FÜ
Ó
MÀÔ
MÔMðQñ 	ô 
ÔkÔTjÔkÔ	kðCÜEPñQñ 	ô
 *;ò 	Ñ%ˆF�G˜XÜ,Ó,OÀ;Ô,OÓOò �Ü˜X {Õ3Ø)1°[Ò)@™gÀhÔR]Ñ]�FÙðò Ùð	ð ˆ>˜hŸm™m×1Ñ1Ó3Ô7UÑUð_Üalñmð ñ 17ˆ_˜T˜F ! F 8Ð,Ð@¸DÐ@r   c                óT   — 	 t        | «      }|rt        |«      ‚y# t        $ r Y yw xY w)a.  Raise ``ValueError`` if ``path`` is a denied Hermes read (see ``get_read_block_error``).

    Shared chokepoint for provider input-loading sites (e.g. image-gen local
    paths). Best-effort: unexpected internal errors no-op rather than break
    local-file loading; a real block still propagates.
    N)rÄ   r
   r)   )r   Úblockeds     r   Úraise_if_read_blockedrÇ   ˆ  s:   € ðÜ& tÓ,ˆñ Ü˜Ó!Ð!ð øô ò Ùðús   ‚ ›	'¦'c                 óÞ   — 	 t        «       j                  «       j                  t        «       j                  «       dz  «      j                  } | r| d   S dS # t
        t        t        f$ r Y yw xY w)z—Active profile name from HERMES_HOME: ``~/.hermes`` -> ``"default"``,
    ``~/.hermes/profiles/X`` -> ``"X"``; ``"default"`` on any resolution failure.ÚprofilesÚdefaultr   )r   r"   r(   r   Úpartsr1   r2   r)   )rË   s    r   Ú_resolve_active_profile_namerÌ   —  sj   € ðÜ!Ó#×+Ñ+Ó-×9Ñ9Ô:KÓ:M×:UÑ:UÓ:WÐZdÑ:dÓe×kÑkˆñ ˆ5�‰8Ð+ )Ð+øô ”\¤:Ð.ò Ùðús   ‚A
A ÁA,Á+A,u[  Sandbox-mirror write blocked by soft guard: {target_path} sits under {mirror_root!r}, which is {body} Use the host-side tool for authoritative state (e.g. ``memory`` for memories), or address the host path directly. To bypass {bypass} with ``cross_profile=True``. (Defense-in-depth â€” not a security boundary; the terminal tool can still bypass.)c                ó2   — t        | «      t        |«      |dœS )z1Common ``classify_*_mirror_target`` result shape.)Útarget_pathÚmirror_rootÚ
inner_path)r+   )ÚtargetrÏ   rÐ   s      r   Ú_mirror_inforÒ   ±  s   € ä˜v›;´s¸;Ó7GÐWaÑbÐbr   c           	     ó  ‡— t        | «      }|�|j                  ndŠt        ˆfd„t        ‰«      D «       d«      }|€y|dz   t	        ‰«      k  rt        t        ‰|dz   d Ž «      nd}t        |t        ‰d|dz    Ž |«      S )uû   Classify a write target as a sandbox-mirror of authoritative Hermes state: ``None``
    for non-mirror paths, else ``target_path`` (resolved), ``mirror_root`` (the
    ``â€¦/home/.hermes`` prefix) and ``inner_path`` (what the agent meant on the host).Nr   c              3  ó†   •K  — | ]8  \  }}|d k(  r.|dz   t        ‰«      k  r‰|dz      dk(  r‰|dz      dk(  r|dz   –— Œ: y­w)Ú	sandboxesé   é   ri   rL   z.hermesN)r?   )rg   ÚiÚpartrË   s      €r   rj   z1classify_sandbox_mirror_target.<locals>.<genexpr>¾  s]   øè ø€ ò 	q‘7�1�dØ�KÒ A¨¡E¬C°«JÒ$6¸5ÀÀQÁ¹<È6Ò;QÐV[Ð\]Ð`aÑ\aÑVbÐfoÒVoð 
ˆQ�ñ 	qùs   ƒ>Ar;   r{   )r4   rË   ÚnextÚ	enumerater?   r+   r   rÒ   )r   rÑ   Ú	inner_idxÚinnerrË   s       @r   Úclassify_sandbox_mirror_targetrÞ   ¶  s•   ø€ ô ˜TÓ"€FØ"Ð.ˆF�LŠL°B€Eäó	qœi¨Ó.ô 	qàó€Ið
 ÐØØ1:¸Q±ÄÀUÃÒ1KŒC”�e˜I¨™M˜NÐ+Ð,Ô-ÐQS€EÜ˜¤ e¨O¨i¸!©mÐ&<Ð =¸uÓEÐEr   c                ób   — | €yt        j                  di | ¤|j                  | d   ¬«      |dœ¤ŽS )z_Render ``_SANDBOX_MIRROR_WARNING`` for a classify_* result (``body`` may use ``{inner_path}``).NrÐ   )rÐ   )ÚbodyÚbypassr   )Ú_SANDBOX_MIRROR_WARNINGÚformat)Úinforà   rá   s      r   Ú_mirror_warningrå   È  s7   € à€|ØÜ"×)Ñ)Ñq¨DÐq°t·{±{ÈdÐS_ÑN`°{Ó7aÐjpÓqÐqr   c                ó.   — t        t        | «      dd«      S )z®Model-facing soft-guard warning when ``path`` lands in a sandbox mirror, else ``None``;
    the caller surfaces it as a tool-result error and ``cross_profile=True`` bypasses.uä   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 {inner_path!r} under the real HERMES_HOME.z8this guard after explicit user direction, retry the call)rå   rÞ   r3   s    r   Úget_sandbox_mirror_warningrç   Ï  s%   € ô Ü& tÓ,ð	Rð 	Cóð r   c                ó¬   — t        | «      |rt        |«      nd}}|�|�t        ||«      syt        |||j                  |«      j	                  «       «      S )us  Classify a write target as a container-side sandbox mirror. Inside the container
    the bind mount strips the ``sandboxes/`` prefix (the agent sees plain ``/root/.hermes/â€¦``),
    so the caller supplies ``mirror_prefix`` once it knows file tools run in a docker sandbox.
    ``None`` without a prefix or outside it, else ``target_path``/``mirror_root``/``inner_path``.N)r4   r/   rÒ   r(   Úas_posix)r   Úmirror_prefixrÑ   Úmirrors       r   Ú classify_container_mirror_targetrì   Û  sT   € ô
 % TÓ*Ém¬O¸MÔ,JÐaeˆF€FØ€~˜˜¬y¸ÀÔ/HØÜ˜ ¨×(:Ñ(:¸6Ó(B×(KÑ(KÓ(MÓNÐNr   c                ó0   — t        t        | |«      dd«      S )z]Model-facing soft-guard warning when ``path`` lands in the container's mirror, else ``None``.u¦   the container's bind-mounted home â€” a per-task mirror that the host Hermes process never reads. The authoritative file is {inner_path!r} under the real HERMES_HOME.z$after explicit user direction, retry)rå   rì   )r   rê   s     r   Úget_container_mirror_warningrî   æ  s#   € äÜ(¨¨}Ó=ð	 ð 	/óð r   )r²   ÚpluginsÚcronÚmemoriesc                ó  — 	 t        t        j                  j                  t	        | «      «      «      j                  «       }t        «       j                  «       }d}d}	 |j                  |«      }|j                  }|sy|d   t        v rd}|d   }n-|d   dk(  r$t        |«      dk\  r|d   t        v r|d   }|d   }nyt        «       }||k(  ry|||t	        |«      dœS # t        t        f$ r Y yw xY w# t        $ r Y yw xY w)	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   rÊ   rÉ   r×   é   r;   )Úactive_profileÚtarget_profileÚarearÎ   )r   r   r   r   r+   r"   r   r1   r2   r(   r)   rË   ÚPROFILE_SCOPED_AREASr?   rÌ   )r   rÑ   Ú	root_realrõ   rö   ÚrelrË   rô   s           r   Úclassify_cross_profile_targetrú   ø  s"  € ð"Ü”b—g‘g×(Ñ(¬¨T«Ó3Ó4×<Ñ<Ó>ˆÜ%Ó'×/Ñ/Ó1ˆ	ð %)€NØ€DðØ× Ñ  Ó+ˆð �I‰I€EÙØàˆQ�xÔ'Ñ'à"ˆØ�Q‰x‰àˆa‰�JÒÜ�‹J˜!ŠOØ�!‰HÔ,Ñ,ð ˜q™ˆØ�Q‰x‰àä1Ó3€NØ˜Ò'àð )Ø(ØÜ˜6“{ñ	ð øôG ”\Ð"ò Ùðûô ò Ùðús$   ‚AC ÁC3 ÃC0Ã/C0Ã3	C?Ã>C?c                 ó   — y)uã  RETIRED (maintainer decision): always returns ``None``.

    The cross-profile write guard was removed â€” profiles were never
    isolated (same OS user; the terminal tool writes anywhere), so the
    block was ceremony that cost every schema real tokens and taught a
    bypass arg. The system prompt's active-profile hint remains the only
    steering; the classifier below survives for that hint and for
    diagnostics. Kept as a stub so external callers/plugins fail soft.
    Nr   r3   s    r   Úget_cross_profile_warningrü   6  s   € ð r   )r   r+   Úreturnr   )rý   r   )rý   z
list[Path])r-   ú
str | Pathr.   rþ   rý   Úbool)r   r+   rý   zOptional[Path])r{   )r   r+   rý   r5   )r   r+   rý   ztuple[set[str], str])r   r+   rý   rÿ   )r   r+   rT   r+   rý   úOptional[str])ri   r+   rý   r5   )ri   r+   rý   z	list[str])rý   r5   )r   r+   rý   r   )r   r+   rý   ÚNone)rý   r+   )rÑ   r   rÏ   r   rÐ   r+   rý   r   )r   r+   rý   úOptional[dict])rä   r  rà   r+   rá   r+   rý   r   rd   )r   r+   rê   ú
str | Nonerý   r  )r   r+   rê   r  rý   r   )2Ú__doc__Ú
__future__r   r   Úpathlibr   Ú
contextlibr   Útypingr   r   r   r   r   r   r/   r4   rH   rJ   rQ   rW   ro   rx   r�   r„   r™   rœ   rž   r¤   r§   r®   Ú__annotations__r¼   r   rf   r»   r½   rÄ   rÇ   rÌ   râ   rÒ   rÞ   rå   rç   rì   rî   r÷   rú   rü   r   r   r   ú<module>r
     sF  ðòõ #ã 	Ý Ý Ý ó5ó.ó
6ó
ZóóGóô0ó2Oó^ð& 6>õ 
ó0óB9óóDð$ kÐ óóD4ð
 6=õ aóOò,Ð  ó ð
 [ð ð �Ð5°vÐ?[Ø‡G�G‡L�L�Ð,Ó-¨r¯w©w¯|©|¸GÐEUÓ/VðÐ ðÐ ó.Aób"ó,ð$Zð ócó
Fó$ró	ôOôð  AÐ ó<ô|
r   