Ë
    ãß²j©"  ã                  ól  — 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 ddlmZ  ej,                  e«      Zd	g g d
œZdZ ej6                  «       Zdaded<   daded<   da ded<    G d„ de!«      Z"dd„Z#d d„Z$d!d„Z%d"d„Z&d#d„Z'd$d%d„Z(d&d„Z)d'd„Z*d$d(d„Z+d)d„Z,y)*aZ  Website access policy helpers for URL-capable tools.

Loads a user-managed website blocklist (``security.website_blocklist`` in ~/.hermes/config.yaml plus
optional shared list files) without the heavier CLI config stack. The parsed policy is cached with a
short TTL so config edits take effect quickly without re-parsing YAML on every URL check.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚOptionalÚTuple)Úurlparse)Úget_hermes_home)Ú_normalize_hostnameF)ÚenabledÚdomainsÚshared_filesg      >@zOptional[Dict[str, Any]]Ú_cached_policyúOptional[str]Ú_cached_policy_pathg        ÚfloatÚ_cached_policy_timec                  ó   — e Zd ZdZy)ÚWebsitePolicyErrorz/Raised when a website policy file is malformed.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© ó    ú9/home/ubuntu/.hermes/hermes-agent/tools/website_policy.pyr   r   !   s   „ Ú9r   r   c                ób  — t        | t        «      r1| j                  «       j                  «       x}r|j	                  d«      ryd|v r%t        |«      }|j                  xs |j                  }|j                  dd«      d   j                  «       j                  d«      j                  d«      xs dS )	z_Reduce a rule (bare host, URL, or ``host/path``) to a lowercase host; None for blanks/comments.ú#Nú://ú/é   r   ú.zwww.)Ú
isinstanceÚstrÚstripÚlowerÚ
startswithr
   ÚnetlocÚpathÚsplitÚrstripÚremoveprefix)ÚruleÚvalueÚparseds      r   Ú_normalize_ruler1   %   s’   € ä�dœCÔ °$·*±*³,×2DÑ2DÓ2FÐ)F¨Ð)FÈ5×K[ÑK[Ð\_ÔK`ØØ��~Ü˜%“ˆØ—‘Ò, §¡ˆØ�;‰;�s˜AÓ˜qÑ!×'Ñ'Ó)×0Ñ0°Ó5×BÑBÀ6ÓJÒRÈdÐRr   c                ó>  — 	 | j                  d¬«      }t        t        |j                  «       «      D �cg c]  }|sŒ|‘Œ	 c}S # t        $ r t        j                  d| «       g cY S t        t
        f$ r#}t        j                  d| |«       g cY d}~S d}~ww xY wc c}w )uŸ   Rules from a shared blocklist file; missing/unreadable files warn and yield nothing rather than
    raising â€” a bad file path must not disable all web tools.úutf-8©Úencodingz.Shared blocklist file not found (skipping): %sz6Failed to read shared blocklist file %s (skipping): %sN)	Ú	read_textÚFileNotFoundErrorÚloggerÚwarningÚOSErrorÚUnicodeDecodeErrorÚmapr1   Ú
splitlines)r*   ÚrawÚexcr.   s       r   Ú_iter_blocklist_file_rulesr@   /   s‹   € ðØ�n‰n gˆnÓ.ˆô !¤°#·.±.Ó2BÓCÖL�TÂtŠDÒLÐLøô ò Ü�‰ÐGÈÔNØŠ	ÜÔ'Ð(ò Ü�‰ÐOÐQUÐWZÔ[Ø�	ûðüò Ms-   ‚A ´B¼BÁ!BÁ&BÁ4BÂBÂBc                óN   — | �t        | t        «      st        |› d�«      ‚| xs i S )zU``None`` (empty YAML section) counts as an empty mapping; other non-dicts are errors.z must be a mapping)r$   Údictr   )r/   Úlabels     r   Ú_require_mappingrD   =   s.   € àÐ¤¨E´4Ô!8Ü  E 7Ð*<Ð!=Ó>Ð>ØŠ;�BÐr   c                óD  — | j                  «       st        t        «      S 	 dd l}	 |j                  | j                  d¬«      «      xs i }t        |t        «      st        d«      ‚t        |j                  d	i «      d	«      }t        |j                  d
i «      d«      }i t        ¥|¥S # t        $ r' t
        j                  d«       t        t        «      cY S w xY w# |j                  $ r}t        d| › d|› �«      |‚d }~wt        $ r}t        d| › d|› �«      |‚d }~ww xY w)Nr   u3   PyYAML not installed â€” website blocklist disabledr3   r4   zInvalid config YAML at z: zFailed to read config file zconfig root must be a mappingÚsecurityÚwebsite_blocklistzsecurity.website_blocklist)ÚexistsrB   Ú_DEFAULT_WEBSITE_BLOCKLISTÚyamlÚImportErrorr8   ÚdebugÚ	safe_loadr6   Ú	YAMLErrorr   r:   r$   rD   Úget)Úconfig_pathrJ   Úconfigr?   rF   rG   s         r   Ú_load_policy_configrR   D   s'  € Ø×ÑÔÜÔ.Ó/Ð/ð0Ûð^Ø—‘ × 5Ñ 5¸wÐ 5Ó GÓHÒNÈBˆô
 �fœdÔ#Ü Ð!@ÓAÐAÜ §
¡
¨:°rÓ :¸JÓG€HÜ(¨¯©Ð6IÈ2Ó)NÐPlÓmÐØ>Ô(Ð>Ð,=Ð>Ð>øô ò 0Ü�‰ÐJÔKÜÔ.Ó/Ò/ð0ûð
 �>‰>ò ZÜ Ð#:¸;¸-ÀrÈ#ÈÐ!OÓPÐVYÐYûÜò ^Ü Ð#>¸{¸mÈ2ÈcÈUÐ!SÓTÐZ]Ð]ûð^ús4   ¡B( ¦%C Â(-CÃCÃDÃ*C<Ã<DÄDÄDc                ó˜   — | j                  ||«      }|t        u r|xs g }t        ||«      s|t        u rdnd}t	        d|› d|› �«      ‚|S )zTTyped policy field; ``None``/empty list values are coerced to ``[]`` for lists only.ÚbooleanÚlistzsecurity.website_blocklist.z must be a )rO   rU   r$   Úboolr   )ÚpolicyÚkeyÚkindÚdefaultr/   Ú	kind_names         r   Ú_require_typer\   Y   sW   € à�J‰J�s˜GÓ$€EØŒt�|Ø’˜ˆÜ�e˜TÔ"Ø!%¬¡‘I°6ˆ	Ü Ð#>¸s¸eÀ;ÈyÈkÐ!ZÓ[Ð[Ø€Lr   c           	     óÒ  — t        «       dz  }t        | xs |«      }t        j                  «       }| €At        5  t
        |k(  xr |t        z
  t        k  }t        �|rt        cddd«       S ddd«       | xs |} t        | «      }t        t        t        |dt        g «      «      }|D �cg c]	  }|sŒ|df‘Œ }}t        |dt        g «      }	t        |dt        d«      }
|	D ]�  }t        |t        «      r|j!                  «       sŒ$t#        |«      j%                  «       }|j'                  «       r|nt        «       |z  j)                  «       }|t+        |«      D �cg c]  }|t        |«      f‘Œ c}z  }Œ’ |
t,        j/                  |«      D ��cg c]
  \  }}||dœ‘Œ c}}d	œ}| |k(  rt        5  |||caaaddd«       |S |S # 1 sw Y   �ŒUxY wc c}w c c}w c c}}w # 1 sw Y   |S xY w)
uË   Parsed website blocklist policy (``{"enabled", "rules"}``); cached for ``_CACHE_TTL_SECONDS`` for
    the default config path only â€” an explicit ``config_path`` (tests) bypasses and never populates it.zconfig.yamlNr   rQ   r   r   T)ÚpatternÚsource)r   Úrules)r   r%   ÚtimeÚ	monotonicÚ_cache_lockr   r   Ú_CACHE_TTL_SECONDSr   rR   r<   r1   r\   rU   rV   r$   r&   r   Ú
expanduserÚis_absoluteÚresolver@   rB   Úfromkeys)rP   Údefault_pathÚresolved_pathÚnowÚfreshrW   r   ÚpÚpairsr   r   Úshared_filer*   Ú
normalizedÚsÚresults                   r   Úload_website_blocklistrs   d   sÞ  € ô #Ó$ }Ñ4€LÜ˜Ò3 |Ó4€MÜ
�.‰.Ó
€CØÐÜñ 	&Ü'¨=Ñ8Òm¸cÔDWÑ>WÔ[mÑ=mˆEÜÐ)©eÜ%÷	&÷ 	&ñ 	&ð Ò- €KÜ  Ó-€FÜ”/¤=°¸ÄDÈ"Ó#MÓN€GØ;BÖ#H°aÂa Q¨¢MÐ#H€EÐ#HÜ  ¨¼¸rÓB€LÜ˜F I¬t°TÓ:€GØ#ò ^ˆÜ˜+¤sÔ+°;×3DÑ3DÔ3FØÜ�KÓ ×+Ñ+Ó-ˆØ×'Ñ'Ô)‰t´Ó0AÀDÑ0H×/QÑ/QÓ/SˆØÔ<VÐW[Ó<\Ö]¨j�:œs 4›yÒ)Ò]Ñ]‰ð^ð !ÔTX×TaÑTaÐbgÓTh×+iÉDÈAÈq¸ÀQÓ,GÓ+iÑj€FØ�lÒ"Üñ 	bØGMÈ}Ð^aÐDˆNÐ/Ð1D÷	bà€Mˆ6€M÷+	&ñ 	&üò $Iùò ^ùã+i÷	bà€Mús/   ¹)F?Â)GÂ1GÅGÆGÆ+GÆ?G	ÇG&c                ó�   — | r|sy|j                  d«      rt        j                  | |«      S | |k(  xs | j                  d|› �«      S )zS``*.example.com`` rules glob-match; bare hosts match exactly or as a parent domain.Fz*.r#   )r(   ÚfnmatchÚendswith)Úhostr^   s     r   Ú_match_host_against_rulerx   „   sG   € á‘wØØ×Ñ˜$ÔÜ�‰˜t WÓ-Ð-Ø�7‰?Ò:˜dŸm™m¨a°¨y¨MÓ:Ð:r   c                óÐ   — t        | «      }t        |j                  xs |j                  «      }|s5d| vr1t        d| › �«      }t        |j                  xs |j                  «      }|S )zPHost of ``url``; schemeless inputs (``example.com/x``) are retried as ``//url``.r    z//)r
   Ú_normalize_hostÚhostnamer)   )Úurlr0   rw   s      r   Ú_extract_host_from_urlishr}   �   sX   € ä�c‹]€FÜ˜6Ÿ?™?Ò;¨f¯m©mÓ<€DÙ�E Ñ$Ü˜B˜s˜e˜*Ó%ˆÜ˜vŸ™Ò?°&·-±-Ó@ˆØ€Kr   c                óp  — |€4t         5  t        �t        j                  d«      s
	 ddd«       yddd«       t        | «      }|sy	 t	        |«      }|j                  d«      sy|j                  dg «      D ]]  }|j                  dd«      |j                  dd	«      }}t        ||«      sŒ4t        j                  d
| ||«       | |||d|› d|› d|› �dœc S  y# 1 sw Y   Œ¨xY w# t
        $ r#}|�‚ t        j                  d|«       Y d}~yd}~wt        $ r }t        j                  d|«       Y d}~yd}~ww xY w)u  ``None`` if the URL is allowed by the blocklist policy, else block metadata (host/rule/source/message).

    Fails open on policy errors (warn + ``None``) so a config typo can't break all web tools â€” except with
    an explicit ``config_path`` (tests), where errors propagate.
    Nr   z.Website policy config error (failing open): %sz:Unexpected error loading website policy (failing open): %sr`   r^   Ú r_   rQ   u,   Blocked URL %s â€” matched rule '%s' from %szBlocked by website policy: 'z' matched rule 'z' from )r|   rw   r.   r_   Úmessage)rc   r   rO   r}   rs   r   r8   r9   Ú	Exceptionrx   Úinfo)r|   rP   rw   rW   r?   r.   r^   r_   s           r   Úcheck_website_accessrƒ   —   sF  € ð ÐÜñ 	ÜÐ)´.×2DÑ2DÀYÔ2OØ÷	÷ 	ð 	ô % SÓ)€DÙØð	Ü'¨Ó4ˆð �:‰:�iÔ ØØ—
‘
˜7 BÓ'ò ˆØŸ(™( 9¨bÓ1°4·8±8¸HÀhÓ3O�ˆÜ# D¨'Õ2Ü�K‰KÐFÈÈWÐV\Ô]à D°'ÀVØ9¸$¸Ð?OÐPWÈyÐX_Ð`fÐ_gÐhñò ð	ð ÷5	ð 	ûô ò ØÐ"ØÜ�‰ÐGÈÔMÜûÜò Ü�‰ÐSÐUXÔYÜûðús/   ‰CÁC" ÃCÃ"	D5Ã+D	Ä	D5ÄD0Ä0D5c                 ó>   — t         5  daddd«       y# 1 sw Y   yxY w)z?Force the next ``check_website_access`` call to re-read config.N)rc   r   r   r   r   Úinvalidate_cacher…   Á   s!   € ô 
ñ Øˆ÷÷ ñ ús   ‡“)r.   r   Úreturnr   )r*   r   r†   z	List[str])r/   r   rC   r%   r†   úDict[str, Any])rP   r   r†   r‡   )
rW   r‡   rX   r%   rY   ÚtyperZ   r   r†   r   )N)rP   úOptional[Path]r†   r‡   )rw   r%   r^   r%   r†   rV   )r|   r%   r†   r%   )r|   r%   rP   r‰   r†   zOptional[Dict[str, str]])r†   ÚNone)-r   Ú
__future__r   ru   ÚloggingÚ	threadingra   Úpathlibr   Útypingr   r   r   r   r	   Úurllib.parser
   Úhermes_constantsr   Útools.url_safetyr   rz   Ú	getLoggerr   r8   rI   rd   ÚLockrc   r   Ú__annotations__r   r   r�   r   r1   r@   rD   rR   r\   rs   rx   r}   rƒ   r…   r   r   r   ú<module>r–      sÅ   ðòõ #ã Û Û Û Ý ß 3Õ 3Ý !å ,Ý Cà	ˆ×	Ñ	˜8Ó	$€à).¸2ÈrÑRÐ ð Ð Øˆi�n‰nÓ€Ø+/€Ð(Ó /Ø%)Ð �]Ó )Ø Ð �UÓ  ô:˜ô :óSóMóó?ó*ôó@;óô"ôTr   