
    `gjL                    l    d Z ddlmZ ddlZddlmZ ddlmZmZ ee	e
e	e	f   Zh dZd
dZddZddd	Zy)u  
AST-level deep audit for skill Python files — opt-in diagnostic, not a security gate.

Per SECURITY.md §2.4, Skills Guard is in-process heuristics ("useful — not
boundaries"). This module is a separate opt-in diagnostic that flags dynamic
import / dynamic attribute access patterns operators may want to eyeball when
reviewing third-party skill code. Every pattern flagged here has legitimate
uses; findings are hints for human review, not verdicts.

CLI: ``hermes skills audit --deep``
    )annotationsN)Path)ListTuple>   .venvvenv__pycache__node_modulesc                   	 t        j                  |       }g  G fddt         j
                        }	  |       j                  |       S # t        t        t        f$ r g cY S w xY w# t        t        t        f$ r Y S w xY w)Nc                  >    e Zd Z fdZ fdZ fdZ fdZy)_scan_source.<locals>.Vc                   |j                   }t        |t        j                        r0|j                  dk(  r!j                  |j                  ddf       nt        |t        j                        rb|j                  dk(  rS|j                  rt        |j                  d   t        j                        sj                  |j                  ddf       nt        |t        j                        rm|j                  dk(  r^t        |j                        d	k\  rFt        |j                  d
   t        j                        sj                  |j                  ddf       | j                  |       y )Nimport_moduledynamic_importu@   importlib.import_module() — loads arbitrary modules at runtime
__import__r   dynamic_import_computedz'__import__ with non-literal module namegetattr      dynamic_getattrz'getattr with non-literal attribute name)func
isinstanceast	AttributeattrappendlinenoNameidargsConstantlengeneric_visit)selfnodeffindingsrel_paths      I/root/.hermes/venv/lib/python3.12/site-packages/tools/skills_ast_audit.py
visit_Callz"_scan_source.<locals>.V.visit_Call"   s   		A!S]]+/0I4;;8H!c!e f Asxx(QTT\-A99Z		!cll%KOOXt{{<U%N%P Q Asxx(QTTY->tyy>Q&z$))A,/UOOXt{{<M%N%P Qt$    c                (   t        |j                  t        j                        r\|j                  j                  dk(  rCt        |j
                  t        j                        sj                  |j                  ddf       | j                  |       y )N__dict__dict_accessu1   __dict__[<computed>] — dynamic attribute access)
r   valuer   r   r   slicer!   r   r   r#   )r$   r%   r'   r(   s     r)   visit_Subscriptz'_scan_source.<locals>.V.visit_Subscript4   se    4::s}}5

:5&tzz3<<@4;;!T!V Wt$r+   c           	         |j                   D ]Z  }|j                  dk(  s|j                  j                  d      s.j                  |j                  dd|j                   df       \ | j                  |       y )N	importlib
importlib.importlib_importzimport u#    — enables dynamic module loading)namesname
startswithr   r   r#   )r$   r%   ar'   r(   s      r)   visit_Importz$_scan_source.<locals>.V.visit_Import=   st    ZZ ]66[(AFF,=,=l,KOOXt{{<N'.qvvh6Y%Z%\ ]] t$r+   c                    |j                   xs d}|dk(  s|j                  d      r#j                  |j                  dd| df       | j	                  |       y )N r3   r4   r5   zfrom u.    import ... — enables dynamic module loading)moduler8   r   r   r#   )r$   r%   mr'   r(   s      r)   visit_ImportFromz(_scan_source.<locals>.V.visit_ImportFromD   s]    !rAK1<<#=4;;8J#(+Y!Z!\ ]t$r+   N)__name__
__module____qualname__r*   r1   r:   r?   )r'   r(   s   r)   Vr   !   s    	%$	%	%	%r+   rC   )r   parseSyntaxError
ValueErrorRecursionErrorNodeVisitorvisitRuntimeError)contentr(   treerC   r'   s    `  @r)   _scan_sourcerM      s    yy! !H(%COO (%T			$
 Ok ^4 	b J5 O	s"   A A, A)(A),BBc                   | j                         rI| j                  j                         dk7  rg S 	 | j                  dd      }t        || j                        S | j                         sg S g }t        | j                  d            D ]x  }t        |j                  j                        t        z  r*	 |j                  dd      }	 |j                  |       j                         }|j#                  t        ||             z |S # t        $ r g cY S w xY w# t        $ r Y w xY w# t         $ r |j                  }Y Vw xY w)zScan a single .py file or recursively scan all .py under a directory.

    Returns a list of (file, line, pattern_id, description) tuples. Empty for
    non-Python paths, missing paths, or paths with no matching patterns.
    z.pyzutf-8replace)encodingerrorsz*.py)is_filesuffixlower	read_textOSErrorrM   r7   is_dirsortedrglobsetparentparts_IGNORED_DIRSrelative_toas_posixrF   extend)pathrK   outpyrels        r)   ast_scan_pathre   T   s*    ||~;;%'I	nnginHG GTYY//;;=	CTZZ'( /ryy-/	llGIlFG	..&//1C 	

<-./ J)  	I	  		  	''C	s5   D 3DD%DD	D"!D"%D=<D=c           	     B   |rd| nd}| s| dS |dt        |        dg}d}t        |       D ]<  \  }}}}||k7  r|}|j                  d|        |j                  d| d| d|        > |j                  d	       |j                  d
       dj                  |      S )z5Plain-text report (Rich-markup-free) grouped by file.zAST deep scan: zAST deep scanz.
  No dynamic import/access patterns detected.z  z finding(s):Nz    Lu     — r<   zA  Note: diagnostic hints for human review, not security verdicts.
)r"   rX   r   join)	r'   
skill_nameheaderlinescurrentr&   linepiddescs	            r)   format_ast_reportrp   v   s    /9zl+FHIIr#h-56EG$X. 84d<GLL2aS"uTF"SEv67	8
 
LL	LLTU99Ur+   )rK   strr(   rq   returnList[Finding])ra   r   rr   rs   )r<   )r'   rs   ri   rq   rr   rq   )__doc__
__future__r   r   pathlibr   typingr   r   rq   intFindingr]   rM   re   rp    r+   r)   <module>r{      sC   
 # 
   S#s"
#@8vDr+   