§
    øžyjt&  ã                  ób  — U d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZmZmZ ddlmZ ddlmZ  ej        e¦  «        Zdg g d	œZd
Z ej        ¦   «         Zdaded<   daded<   daded<   d.d„Z G d„ de¦  «        Z d/d„Z!d0d„Z"d1d„Z#d2d3d#„Z$d2d3d$„Z%d4d&„Z&d5d)„Z'd6d+„Z(d2d7d-„Z)dS )8a¤  Website access policy helpers for URL-capable tools.

This module loads a user-managed website blocklist from ~/.hermes/config.yaml
and optional shared list files. It is intentionally lightweight so web/browser
tools can enforce URL policy without pulling in the heavier CLI config stack.

Policy is cached in memory with a short TTL so config changes take effect
quickly without re-reading the file on every URL check.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚOptionalÚTuple)Úurlparse©Úget_hermes_homeF)ÚenabledÚdomainsÚshared_filesg      >@zOptional[Dict[str, Any]]Ú_cached_policyúOptional[str]Ú_cached_policy_pathg        ÚfloatÚ_cached_policy_timeÚreturnr   c                 ó$   — t          ¦   «         dz  S )Nzconfig.yamlr   © ó    ú:/home/ragecks/.hermes/hermes-agent/tools/website_policy.pyÚ_get_default_config_pathr   (   s   € ÝÑÔ˜}Ñ,Ð,r   c                  ó   — e Zd ZdZdS )ÚWebsitePolicyErrorz/Raised when a website policy file is malformed.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r   r   r   ,   s   € € € € € Ø9Ð9Ð9Ð9r   r   ÚhostÚstrc                óx   — | pd                      ¦   «                              ¦   «                              d¦  «        S )NÚ ú.)ÚstripÚlowerÚrstrip)r!   s    r   Ú_normalize_hostr)   0   s2   € ØˆJ�B×ÒÑÔ×%Ò%Ñ'Ô'×.Ò.¨sÑ3Ô3Ð3r   Úruler   c                ó¸  — t          | t          ¦  «        sd S |                      ¦   «                              ¦   «         }|r|                     d¦  «        rd S d|v rt          |¦  «        }|j        p|j        }|                     dd¦  «        d                              ¦   «          	                    d¦  «        }|                     d¦  «        r
|dd …         }|pd S )	Nú#ú://ú/é   r   r%   zwww.é   )
Ú
isinstancer"   r&   r'   Ú
startswithr
   ÚnetlocÚpathÚsplitr(   )r*   ÚvalueÚparseds      r   Ú_normalize_ruler8   4   sÕ   € Ý�d�CÑ Ô ð ØˆtØ�JŠJ‰LŒL×ÒÑ Ô €EØð �E×$Ò$ SÑ)Ô)ð ØˆtØ�€~€~Ý˜%‘”ˆØ”Ð, ¤ˆØ�KŠK˜˜QÑÔ Ô"×(Ò(Ñ*Ô*×1Ò1°#Ñ6Ô6€EØ×Ò˜ÑÔð Ø�a�b�b”	ˆØˆ=�DÐr   r4   ú	List[str]c                óÔ  — 	 |                       d¬¦  «        }nd# t          $ r  t                               d| ¦  «         g cY S t          t
          f$ r(}t                               d| |¦  «         g cY d}~S d}~ww xY wg }|                     ¦   «         D ]T}|                     ¦   «         }|r|                     d¦  «        rŒ.t          |¦  «        }|r| 
                    |¦  «         ŒU|S )uÃ   Load rules from a shared blocklist file.

    Missing or unreadable files log a warning and return an empty list
    rather than raising â€” a bad file path should not disable all web tools.
    úutf-8©Úencodingz.Shared blocklist file not found (skipping): %sz6Failed to read shared blocklist file %s (skipping): %sNr,   )Ú	read_textÚFileNotFoundErrorÚloggerÚwarningÚOSErrorÚUnicodeDecodeErrorÚ
splitlinesr&   r2   r8   Úappend)r4   ÚrawÚexcÚrulesÚlineÚstrippedÚ
normalizeds          r   Ú_iter_blocklist_file_rulesrL   C   s  € ðØ�nŠn gˆnÑ.Ô.ˆˆøÝð ð ð Ý�ŠÐGÈÑNÔNÐNØˆ	ˆ	ˆ	ÝÕ'Ð(ð ð ð Ý�ŠÐOÐQUÐWZÑ[Ô[Ð[Øˆ	ˆ	ˆ	ˆ	ˆ	ˆ	øøøøðøøøð €EØ—’Ñ Ô ð %ð %ˆØ—:’:‘<”<ˆØð 	˜8×.Ò.¨sÑ3Ô3ð 	ØÝ$ XÑ.Ô.ˆ
Øð 	%Ø�LŠL˜Ñ$Ô$Ð$øØ€Ls!   ‚ ™'A:ÁA:ÁA5Á/A:Á5A:Úconfig_pathúOptional[Path]úDict[str, Any]c                óª  — | pt          ¦   «         } |                      ¦   «         st          t          ¦  «        S 	 dd l}n># t
          $ r1 t                               d¦  «         t          t          ¦  «        cY S w xY w	 t          | d¬¦  «        5 }| 	                    |¦  «        pi }d d d ¦  «         n# 1 swxY w Y   nK# |j
        $ r}t          d| › d|› �¦  «        |‚d }~wt          $ r}t          d| › d|› �¦  «        |‚d }~ww xY wt          |t          ¦  «        st          d¦  «        ‚|                     d	i ¦  «        }|€i }t          |t          ¦  «        st          d
¦  «        ‚|                     di ¦  «        }|€i }t          |t          ¦  «        st          d¦  «        ‚t          t          ¦  «        }|                     |¦  «         |S )Nr   u3   PyYAML not installed â€” website blocklist disabledr;   r<   zInvalid config YAML at z: zFailed to read config file zconfig root must be a mappingÚsecurityzsecurity must be a mappingÚwebsite_blocklistz,security.website_blocklist must be a mapping)r   ÚexistsÚdictÚ_DEFAULT_WEBSITE_BLOCKLISTÚyamlÚImportErrorr@   ÚdebugÚopenÚ	safe_loadÚ	YAMLErrorr   rB   r1   ÚgetÚupdate)rM   rV   ÚfÚconfigrG   rQ   rR   Úpolicys           r   Ú_load_policy_configra   ]   sX  € ØÐ;Õ!9Ñ!;Ô!;€KØ×ÒÑÔð 0ÝÕ.Ñ/Ô/Ð/ð0ØˆˆˆˆøÝð 0ð 0ð 0Ý�ŠÐJÑKÔKÐKÝÕ.Ñ/Ô/Ð/Ð/Ð/ð0øøøð^Ý�+¨Ð0Ñ0Ô0ð 	-°AØ—^’^ AÑ&Ô&Ð,¨"ˆFð	-ð 	-ð 	-ñ 	-ô 	-ð 	-ð 	-ð 	-ð 	-ð 	-ð 	-øøøð 	-ð 	-ð 	-ð 	-øøàŒ>ð Zð Zð ZÝ Ð!O¸;Ð!OÐ!OÈ#Ð!OÐ!OÑPÔPÐVYÐYøøøøÝð ^ð ^ð ^Ý Ð!S¸{Ð!SÐ!SÈcÐ!SÐ!SÑTÔTÐZ]Ð]øøøøð^øøøå�f�dÑ#Ô#ð BÝ Ð!@ÑAÔAÐAà�zŠz˜* bÑ)Ô)€HØÐØˆÝ�h¥Ñ%Ô%ð ?Ý Ð!=Ñ>Ô>Ð>à ŸšÐ%8¸"Ñ=Ô=ÐØÐ ØÐÝÐ'­Ñ.Ô.ð QÝ Ð!OÑPÔPÐPåÕ,Ñ-Ô-€FØ
‡M‚MÐ#Ñ$Ô$Ð$Ø€MsW   º? ¿8A:Á9A:Á>B? ÂB3Â'B? Â3B7Â7B? Â:B7Â;B? Â?
DÃ	CÃDÃ,DÄDc                óÜ  — t          t          ¦   «         ¦  «        }| rt          | ¦  «        n|}t          j        ¦   «         }| €Wt          5  t
          �1t          |k    r&|t          z
  t          k     rt
          cddd¦  «         S ddd¦  «         n# 1 swxY w Y   | pt          ¦   «         } t          | ¦  «        }| 
                    dg ¦  «        pg }t          |t          ¦  «        st          d¦  «        ‚| 
                    dg ¦  «        pg }t          |t          ¦  «        st          d¦  «        ‚| 
                    dd¦  «        }t          |t          ¦  «        st          d¦  «        ‚g }t          ¦   «         }	|D ]H}
t!          |
¦  «        }|r5d	|f|	vr/|                     |d	d
œ¦  «         |	                     d	|f¦  «         ŒI|D ]æ}t          |t           ¦  «        r|                     ¦   «         sŒ,t)          |¦  «                             ¦   «         }|                     ¦   «         s#t/          ¦   «         |z                       ¦   «         }t3          |¦  «        D ]R}t          |¦  «        |f}||	v rŒ|                     |t          |¦  «        d
œ¦  «         |	                     |¦  «         ŒSŒç||dœ}| t          ¦   «         k    r%t          5  |a|a|addd¦  «         n# 1 swxY w Y   |S )zïLoad and return the parsed website blocklist policy.

    Results are cached for ``_CACHE_TTL_SECONDS`` to avoid re-reading
    config.yaml on every URL check.  Pass an explicit ``config_path``
    to bypass the cache (used by tests).
    Nr   z1security.website_blocklist.domains must be a listr   z6security.website_blocklist.shared_files must be a listr   Tz4security.website_blocklist.enabled must be a booleanr_   )ÚpatternÚsource)r   rH   )r"   r   ÚtimeÚ	monotonicÚ_cache_lockr   r   r   Ú_CACHE_TTL_SECONDSra   r\   r1   Úlistr   ÚboolÚsetr8   rE   Úaddr&   r   Ú
expanduserÚis_absoluter   ÚresolverL   )rM   Údefault_pathÚresolved_pathÚnowr`   Úraw_domainsÚraw_shared_filesr   rH   ÚseenÚraw_rulerK   Úshared_filer4   ÚkeyÚresults                   r   Úload_website_blocklistrz   ƒ   sª  € õ Õ/Ñ1Ô1Ñ2Ô2€LØ(3ÐE•C˜Ñ$Ô$Ð$¸€MÝ
Œ.Ñ
Ô
€Cð ÐÝð 	&ð 	&åÐ*Ý'¨=Ò8Ð8ØÕ.Ñ.Õ2DÒDÐDå%ð	&ð 	&ð 	&ð 	&ñ 	&ô 	&ð 	&ð 	&ð 	&ð 	&ð 	&ñ 	&ô 	&ð 	&ð 	&ð 	&ð 	&ð 	&ð 	&øøøð 	&ð 	&ð 	&ð 	&ð Ð;Õ!9Ñ!;Ô!;€KÝ  Ñ-Ô-€Fà—*’*˜Y¨Ñ+Ô+Ð1¨r€KÝ�k¥4Ñ(Ô(ð VÝ Ð!TÑUÔUÐUà—z’z .°"Ñ5Ô5Ð;¸ÐÝÐ&­Ñ-Ô-ð [Ý Ð!YÑZÔZÐZà�jŠj˜ DÑ)Ô)€GÝ�g�tÑ$Ô$ð YÝ Ð!WÑXÔXÐXà"$€EÝ!$¡¤€Dàð -ð -ˆÝ$ XÑ.Ô.ˆ
Øð 	-˜8 ZÐ0¸Ð<Ð<Ø�LŠL Z¸8ÐDÐDÑEÔEÐEØ�HŠH�h 
Ð+Ñ,Ô,Ð,øà'ð ð ˆÝ˜+¥sÑ+Ô+ð 	°;×3DÒ3DÑ3FÔ3Fð 	ØÝ�KÑ Ô ×+Ò+Ñ-Ô-ˆØ×ÒÑ!Ô!ð 	8Ý#Ñ%Ô%¨Ñ,×5Ò5Ñ7Ô7ˆDÝ4°TÑ:Ô:ð 	ð 	ˆJÝ�t‘9”9˜jÐ)ˆCØ�dˆ{ˆ{ØØ�LŠL Z½3¸t¹9¼9ÐEÐEÑFÔFÐFØ�HŠH�S‰MŒMˆMˆMð	ð !¨5Ð1Ð1€Fð Õ.Ñ0Ô0Ò0Ð0Ýð 	&ð 	&Ø#ˆNØ"/ÐØ"%Ðð	&ð 	&ð 	&ñ 	&ô 	&ð 	&ð 	&ð 	&ð 	&ð 	&ð 	&øøøð 	&ð 	&ð 	&ð 	&ð
 €Ms$   Á,BÂBÂBËK!Ë!K%Ë(K%ÚNonec                 óJ   — t           5  daddd¦  «         dS # 1 swxY w Y   dS )z?Force the next ``check_website_access`` call to re-read config.N)rg   r   r   r   r   Úinvalidate_cacher}   Ë   ss   € õ 
ð ð Øˆðð ð ñ ô ð ð ð ð ð ð ð øøøð ð ð ð ð ð s   ˆ˜Ÿrc   rj   c                óž   — | r|sdS |                      d¦  «        rt          j        | |¦  «        S | |k    p|                      d|› �¦  «        S )NFz*.r%   )r2   ÚfnmatchÚendswith)r!   rc   s     r   Ú_match_host_against_ruler�   Ò   s_   € Øð �wð ØˆuØ×Ò˜$ÑÔð .ÝŒ˜t WÑ-Ô-Ð-Ø�7Š?Ð:˜dŸmšm¨M°¨M¨MÑ:Ô:Ð:r   Úurlc                óÌ   — t          | ¦  «        }t          |j        p|j        ¦  «        }|r|S d| vr1t          d| › �¦  «        }t          |j        p|j        ¦  «        }|r|S dS )Nr-   z//r$   )r
   r)   Úhostnamer3   )r‚   r7   r!   Ú
schemelesss       r   Ú_extract_host_from_urlishr†   Ú   sx   € Ý�c‰]Œ]€FÝ˜6œ?Ð;¨f¬mÑ<Ô<€DØð Øˆà�CÐÐÝ˜j 3˜j˜jÑ)Ô)ˆ
Ý˜zÔ2ÐG°jÔ6GÑHÔHˆØð 	ØˆKàˆ2r   úOptional[Dict[str, str]]c                ó:  — |€Nt           5  t          �(t                               d¦  «        s	 ddd¦  «         dS ddd¦  «         n# 1 swxY w Y   t          | ¦  «        }|sdS 	 t	          |¦  «        }nd# t
          $ r)}|�‚ t                               d|¦  «         Y d}~dS d}~wt          $ r&}t                               d|¦  «         Y d}~dS d}~ww xY w|                     d¦  «        sdS |                     dg ¦  «        D ]”}|                     dd¦  «        }t          ||¦  «        rlt           
                    d| ||                     d	d
¦  «        ¦  «         | |||                     d	d
¦  «        d|› d|› d|                     d	d
¦  «        › �dœc S Œ•dS )u£  Check whether a URL is allowed by the website blocklist policy.

    Returns ``None`` if access is allowed, or a dict with block metadata
    (``host``, ``rule``, ``source``, ``message``) if blocked.

    Never raises on policy errors â€” logs a warning and returns ``None``
    (fail-open) so a config typo doesn't break all web tools.  Pass
    ``config_path`` explicitly (tests) to get strict error propagation.
    Nr   z.Website policy config error (failing open): %sz:Unexpected error loading website policy (failing open): %srH   rc   r$   u,   Blocked URL %s â€” matched rule '%s' from %srd   r_   zBlocked by website policy: 'z' matched rule 'z' from )r‚   r!   r*   rd   Úmessage)rg   r   r\   r†   rz   r   r@   rA   Ú	Exceptionr�   Úinfo)r‚   rM   r!   r`   rG   r*   rc   s          r   Úcheck_website_accessrŒ   é   sc  € ð ÐÝð 	ð 	ÝÐ)µ.×2DÒ2DÀYÑ2OÔ2OÐ)Øð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	õ % SÑ)Ô)€DØð Øˆtð	Ý'¨Ñ4Ô4ˆˆøÝð ð ð ØÐ"ØÝ�ŠÐGÈÑMÔMÐMØˆtˆtˆtˆtˆtøøøøÝð ð ð Ý�ŠÐSÐUXÑYÔYÐYØˆtˆtˆtˆtˆtøøøøðøøøð �:Š:�iÑ Ô ð Øˆtà—
’
˜7 BÑ'Ô'ð ð ˆØ—(’(˜9 bÑ)Ô)ˆÝ# D¨'Ñ2Ô2ð 	Ý�KŠKÐFØ˜W d§h¢h¨x¸Ñ&BÔ&BñDô Dð Dð ØØØŸ(š( 8¨XÑ6Ô6ð<°4ð <ð <Èð <ð <Ø!ŸXšX h°Ñ9Ô9ð<ð <ð	ð 	ð 	ð 	ð 	ð	ð ˆ4s5   Š#AÁA
ÁA
Á%A5 Á5
CÁ?B#Â#CÂ0CÃC)r   r   )r!   r"   r   r"   )r*   r   r   r   )r4   r   r   r9   )N)rM   rN   r   rO   )r   r{   )r!   r"   rc   r"   r   rj   )r‚   r"   r   r"   )r‚   r"   rM   rN   r   r‡   )*r    Ú
__future__r   r   ÚloggingÚ	threadingre   Úpathlibr   Útypingr   r   r   r   r	   Úurllib.parser
   Úhermes_constantsr   Ú	getLoggerr   r@   rU   rh   ÚLockrg   r   Ú__annotations__r   r   r   rŠ   r   r)   r8   rL   ra   rz   r}   r�   r†   rŒ   r   r   r   ú<module>r—      s7  ððð ð ð #Ð "Ð "Ð "Ð "Ð "à €€€Ø €€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð Ø 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ø !Ð !Ð !Ð !Ð !Ð !à ,Ð ,Ð ,Ð ,Ð ,Ð ,à	ˆÔ	˜8Ñ	$Ô	$€ð ØØðð Ð ð Ð ØˆiŒnÑÔ€Ø+/€Ð /Ð /Ð /Ñ /Ø%)Ð Ð )Ð )Ð )Ñ )Ø Ð Ð  Ð  Ð  Ñ  ð-ð -ð -ð -ð:ð :ð :ð :ð :˜ñ :ô :ð :ð4ð 4ð 4ð 4ðð ð ð ðð ð ð ð4#ð #ð #ð #ð #ðLEð Eð Eð Eð EðPð ð ð ð;ð ;ð ;ð ;ðð ð ð ð2ð 2ð 2ð 2ð 2ð 2ð 2r   