
    i	f              	          U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlmZ  ej        e          ZdZde dZdZded	ed
efdZded	ed
efdZd
efdZdaedz  ez  ed<   dZ da!eed<    ej"                    Z#da$ej%        dz  ed<   dZ&d
efdZ'd
efdZ(d
edz  fdZ)d
efdZ*d6defdZ+d Z,d
efdZ-d
edz  fdZ.d7deded efd!Z/d"ed#ed$ed
edz  fd%Z0d&ed"ed'ed
efd(Z1d)d*d+ed
e2edz  ef         fd,Z3d-ed
efd.Z4d-ed
efd/Z5d)d*d+efd0Z6d)d*d+efd1Z7d2Z8d3Z9d4ed
efd5Z:dS )8u  Tirith pre-exec security scanning wrapper.

Runs the tirith binary as a subprocess to scan commands for content-level
threats (homograph URLs, pipe-to-interpreter, terminal injection, etc.).

Exit code is the verdict source of truth:
  0 = allow, 1 = block, 2 = warn

JSON stdout enriches findings/summary but never overrides the verdict.
Operational failures (spawn error, timeout, unknown exit code) respect
the fail_open config setting. Programming errors propagate.

Auto-install: if tirith is not found on PATH or at the configured path,
it is automatically downloaded from GitHub releases to $HERMES_HOME/bin/tirith.
The download always verifies SHA-256 checksums.  When cosign is available on
PATH, provenance verification (GitHub Actions workflow signature) is also
performed.  If cosign is not installed, the download proceeds with SHA-256
verification only — still secure via HTTPS + checksum, just without supply
chain provenance proof.  Installation runs in a background thread so startup
never blocks.
    N)get_hermes_homezsheeki03/tirithz^https://github.com/z,/\.github/workflows/release\.yml@refs/tags/vz+https://token.actions.githubusercontent.comkeydefaultreturnc                 ^    t          j        |           }||S |                                dv S )N)1trueyes)osgetenvlowerr   r   vals      :/home/ubuntu/.hermes/hermes-agent/tools/tirith_security.py	_env_boolr   3   s-    
)C..C
{99;;...    c                 v    t          j        |           }||S 	 t          |          S # t          $ r |cY S w xY w)N)r   r   int
ValueErrorr   s      r   _env_intr   :   sM    
)C..C
{3xx   s   ) 88c                     ddddd} 	 ddl m}  |                                di           pi }n# t          $ r i }Y nw xY wt	          d|                    d	| d	                             t          j        d
|                    d| d                             t          d|                    d| d                             t	          d|                    d| d                             dS )z@Load security settings from config.yaml, with env var overrides.Ttirith   )tirith_enabledtirith_pathtirith_timeouttirith_fail_openr   )load_configsecurityTIRITH_ENABLEDr   
TIRITH_BINr   TIRITH_TIMEOUTr   TIRITH_FAIL_OPENr   )hermes_cli.configr   get	Exceptionr   r   r   r   )defaultsr   cfgs      r   _load_security_configr)   D   s     	 H111111kmm
B//52    $$4cgg>NPXYiPj6k6kllysww}h}F]/^/^__"#3SWW=MxXhOi5j5jkk%&8#''BTV^_qVr:s:stt	  s   &0 ??_resolved_pathF _install_failure_reason_install_threadiQ c                  8    t          t                                S )zAReturn the Hermes home directory, respecting HERMES_HOME env var.)strr    r   r   _get_hermes_homer1   l   s      !!!r   c                  Z    t           j                            t                      d          S )z3Return the path to the install-failure marker file.z.tirith-install-failed)r   pathjoinr1   r0   r   r   _failure_marker_pathr5   q   s     7<<(**,DEEEr   c                  b   	 t                      } t          j                            |           }t	          j                    |z
  t
          k    rdS t          | d          5 }|                                                                cddd           S # 1 swxY w Y   dS # t          $ r Y dS w xY w)zRead the failure reason from the disk marker.

    Returns the reason string, or None if the marker doesn't exist or is
    older than _MARKER_TTL.
    Nr)
r5   r   r3   getmtimetime_MARKER_TTLopenreadstripOSError)pmtimefs      r   _read_failure_reasonrB   v   s     ""  ##IKK%K//4!S\\ 	$Q6688>>##	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$   tts<   AB  B   &BB  BB  BB   
B.-B.c                  ~    t                      } | dS | dk    r$t          j        d          rt                       dS dS )zCheck if a recent install failure was persisted to disk.

    Returns False (allowing retry) when:
    - No marker exists
    - Marker is older than _MARKER_TTL (24h)
    - Marker reason is 'cosign_missing' and cosign is now on PATH
    NFcosign_missingcosignT)rB   shutilwhich_clear_install_failed)reasons    r   _is_install_failed_on_diskrJ      sJ     "##F~u!!!fl8&<&<!u4r   rI   c                 (   	 t                      }t          j        t          j                            |          d           t          |d          5 }|                    |            ddd           dS # 1 swxY w Y   dS # t          $ r Y dS w xY w)a  Persist install failure to disk to avoid retry on next process.

    Args:
        reason: Short tag identifying the failure cause. Use "cosign_missing"
                when cosign is not on PATH so the marker can be auto-cleared
                once cosign becomes available.
    Texist_okwN)r5   r   makedirsr3   dirnamer;   writer>   )rI   r?   rA   s      r   _mark_install_failedrR      s     ""
BGOOA&&6666!S\\ 	QGGFOOO	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	   s6   AB A6)B 6A::B =A:>B 
BBc                  j    	 t          j        t                                 dS # t          $ r Y dS w xY w)z3Remove the failure marker after successful install.N)r   unlinkr5   r>   r0   r   r   rH   rH      sF    
	&(()))))   s    $ 
22c                      t           j                            t                      d          } t          j        | d           | S )z/Return $HERMES_HOME/bin, creating it if needed.binTrL   )r   r3   r4   r1   rO   )ds    r   _hermes_bin_dirrX      s8    
%''//AKD!!!!Hr   c                      t          j                    } t          j                                                    }| dk    rd}n	| dv rd}ndS |dv rd}n	|dv rd	}ndS | d
| S )z@Return the Rust target triple for the current platform, or None.Darwinzapple-darwin)LinuxAndroidzunknown-linux-gnuN)x86_64amd64r]   )aarch64arm64r_   -)platformsystemmachiner   )rc   rd   platarchs       r   _detect_targetrg      s    _F  &&((G 	'	'	'"t%%%	(	(	(tTr   
   urldesttimeoutc                    t           j                            |           }t          j        d          }|r|                    dd|            t           j                            ||          5 }t          |d          5 }t          j	        ||           ddd           n# 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )zDownload a URL to a local file.GITHUB_TOKENAuthorizationztoken )rk   wbN)
urllibrequestRequestr   r   
add_headerurlopenr;   rF   copyfileobj)ri   rj   rk   reqtokenresprA   s          r   _download_filery      s<   
.
 
 
%
%CIn%%E :(8(8(8999			W		5	5 $tD$?O?O $ST4###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s6   0B;B#B;#B'	'B;*B'	+B;;B?B?checksums_pathsig_path	cert_pathc                    t          j        d          }|st                              d           dS 	 t	          j        |dd|d|dt          dt          | gd	d	d
          }|j        dk    rt                              d           d	S t          	                    d|j        |j
                                                   dS # t          t          j        f$ r&}t          	                    d|           Y d}~dS d}~ww xY w)uk  Verify cosign provenance signature on checksums.txt.

    Returns:
        True  — cosign verified successfully
        False — cosign found but verification failed
        None  — cosign not available (not on PATH, or execution failed)

    The caller treats both False and None as "abort auto-install" — only
    True allows the install to proceed.
    rE   zcosign not found on PATHNzverify-blobz--certificatez--signaturez--certificate-identity-regexpz--certificate-oidc-issuerT   capture_outputtextrk   r   z%cosign provenance verification passedz(cosign verification failed (exit %d): %sFzcosign execution failed: %s)rF   rG   loggerinfo
subprocessrun_COSIGN_IDENTITY_REGEXP_COSIGN_ISSUER
returncodewarningstderrr=   r>   TimeoutExpired)rz   r{   r|   rE   resultexcs         r   _verify_cosignr      s#    \(##F .///t]iH,.E(.  

 

 

 !!KK?@@@4NNE +V]-@-@-B-BD D D5Z./   4c:::ttttts   AC 8C C>C99C>archive_patharchive_namec                   	 d}t          |          5 		D ]S}|                                                    dd          }t          |          dk    r|d         |k    r
|d         } nTddd           n# 1 swxY w Y   |st                              d|           dS t          j                    }t          | d          5 	t          	fd	d
          D ]}|	                    |           	 ddd           n# 1 swxY w Y   |
                                }||k    rt                              d||           dS dS )z4Verify SHA-256 of the archive against checksums.txt.Nz        r   zNo checksum entry for %sFrbc                  .                          d          S )Ni    )r<   )rA   s   r   <lambda>z"_verify_checksum.<locals>.<lambda>  s    !&&,, r   r   z&Checksum mismatch: expected %s, got %sT)r;   r=   splitlenr   r   hashlibsha256iterupdate	hexdigest)
r   rz   r   expectedlinepartsshachunkactualrA   s
            @r   _verify_checksumr     s   H	n		  	 	DJJLL&&tQ//E5zzQ58|#;#; 8                1<@@@u
.

C	lD	!	! Q....44 	 	EJJu	               ]]__F?6RRRu4s$   AA66A:=A:,C<<D D Tlog_failuresr   c                 	   | rt           j        nt           j        }t                      }|s@t                               dt          j                    t          j                               dS d| d}dt           d}t          j
        d          }	 t          j                            ||          }t          j                            |d	          }t          j                            |d
          }t          j                            |d          }	t                               d|           	 t          | d| |           t          | d|           n:# t          $ r-}
 |d|
           Y d}
~
t!          j        |d           dS d}
~
ww xY wd}t!          j        d          r	 t          | d|           t          | d|	           t'          |||	          }|du rd}n|du r$ |d           	 t!          j        |d           dS t                               d           nL# t          $ r%}
t                               d|
           Y d}
~
n"d}
~
ww xY wt                               d           t)          |||          s	 t!          j        |d           dS t+          j        |d          5 }|                                D ]P}|j        dk    s|j                            d           r)d!|j        v r1d|_        |                    ||            n0Q |d"           	 ddd           t!          j        |d           d#S 	 ddd           n# 1 swxY w Y   t          j                            |d          }t          j                            t7                      d          }	 t!          j        ||           nu# t:          $ rh 	 t!          j        ||           nN# t:          $ rA 	 t          j        |           n# t:          $ r Y nw xY wY Y t!          j        |d           d$S w xY wY nw xY wt          j         |t          j!        |          j"        tB          j#        z  tB          j$        z  tB          j%        z             |rd%nd&}t                               d'||           |d(ft!          j        |d           S # t!          j        |d           w xY w))a^  Download and install tirith to $HERMES_HOME/bin/tirith.

    Verifies provenance via cosign and SHA-256 checksum.
    Returns (installed_path, failure_reason).  On success failure_reason is "".
    failure_reason is a short tag used by the disk marker to decide if the
    failure is retryable (e.g. "cosign_missing" clears when cosign appears).
    z/tirith auto-install: unsupported platform %s/%s)Nunsupported_platformztirith-z.tar.gzzhttps://github.com/z/releases/latest/downloadztirith-install-)prefixzchecksums.txtzchecksums.txt.sigzchecksums.txt.pemu9   tirith not found — downloading latest release for %s.../z/checksums.txtztirith download failed: %sNT)ignore_errors)Ndownload_failedFrE   z/checksums.txt.sigz/checksums.txt.pemz=tirith install aborted: cosign provenance verification failed)Ncosign_verification_failedz5cosign execution failed, proceeding with SHA-256 onlyz?cosign artifacts unavailable (%s), proceeding with SHA-256 onlyu{   cosign not on PATH — installing tirith with SHA-256 verification only (install cosign for full supply chain verification))Nchecksum_failedzr:gzr   z/tirithz..z"tirith binary not found in archive)Nbinary_not_in_archive)Ncross_device_copy_failedzcosign + SHA-256zSHA-256 onlyztirith installed to %s (%s)r+   )&r   r   debugrg   r   rb   rc   rd   _REPOtempfilemkdtempr   r3   r4   ry   r&   rF   rmtreerG   r   r   tarfiler;   
getmembersnameendswithextractrX   mover>   copyrT   chmodstatst_modeS_IXUSRS_IXGRPS_IXOTH)r   logtargetr   base_urltmpdirr   rz   r{   r|   r   cosign_verifiedcosign_resulttarmembersrcrj   verifications                     r   _install_tirithr     s    )
:&..flCF ,E_&&(8(:(:	< 	< 	<++,V,,,LEUEEEH%6777FT2w||FL99fo>>7<<(;<<GLL)<==	OQWXXX	+h7777FFFh666GGGG 	+ 	+ 	+C,c222****N 	fD111111S	+  <!! 	OY(>>>III(>>>	JJJ !/~x S S D((&*OO"e++ CWXXX=b 	fD111111[ KK WXXXX  d d d]_bccccccccd  KK N O O O  nlKK 	+*N 	fD111111K \,// 	53..** 	5 	5;(**fk.B.B9.M.M*v{** "*FKKK///E + 89994	5 	5 	5 	5 	5 	5J 	fD111111; 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 gll68,,w||O--x88	8KT"""" 	8 	8 	88C&&&& 8 8 8IdOOOO   D77 	fD1111118 '&	8 	rwt}},t|;dlJT\YZZZ-<P)).14FFFRx 	fD11111fD11111s  BS .(E S 
F!F	-S 	FS (&H) 'S S )
I3IS I.S S 4A2M&S 
S MS MAS .O S 
P6O%$P6%
P00PP0
PP0PP0P6S /P00P63S 5P66A;S S configured_pathc                     | dk    S )zHReturn True if the user explicitly configured a non-default tirith path.r   r0   )r   s    r   _is_explicit_pathr     s    h&&r   c                    t           t           t          urt           S t          j                            |           }t          |           }t           t          u }|rt          j                            |          r#t          j        |t          j                  r|a |S t          j
        |          }|r|a |S t                              d|            t          a da|S t          j
        d          }|r|a dat                       |S t          j                            t!                      d          }t          j                            |          r3t          j        |t          j                  r|a dat                       |S |r6t          dk    r)t          j
        d          rda dat                       d}n|S t"          t"                                          r|S t'                      }|t)                      rt          a |a|S t+                      \  }}|r|a dat                       |S t          a |at-          |           |S )	u@  Resolve the tirith binary path, auto-installing if necessary.

    If the user explicitly set a path (anything other than the bare "tirith"
    default), that path is authoritative — we never fall through to
    auto-download a different binary.

    For the default "tirith":
    1. PATH lookup via shutil.which
    2. $HERMES_HOME/bin/tirith (previously auto-installed)
    3. Auto-install from GitHub releases → $HERMES_HOME/bin/tirith

    Failed installs are cached for the process lifetime (and persisted to
    disk for 24h) to avoid repeated network attempts.
    Nz6Configured tirith path %r not found; scanning disabledexplicit_path_missingr   r+   rD   rE   F)r*   _INSTALL_FAILEDr   r3   
expanduserr   isfileaccessX_OKrF   rG   r   r   r,   rH   r4   rX   r-   is_aliverB   rJ   r   rR   )	r   expandedexplicitinstall_failedfound
hermes_bindisk_reason	installedrI   s	            r   _resolve_tirith_pathr     sN   $ !nO&K&Kw!!/22H 11H#6N  7>>(## 		(BG(D(D 	%NOX&& 	"NLOQ`aaa("9
 L""E "$o//::J	w~~j!! bi
BG&D&D #"$
  "&6666<;Q;Q6!N&(#!###"NNO
 "'?'?'A'A"
 '((K#=#?#?("-'))Iv ""$ %N$   Or   c                 8   t           5  t          	 ddd           dS t          j        d          }|r|ada	 ddd           dS t
          j                            t                      d          }t
          j        	                    |          r1t          j
        |t
          j                  r|ada	 ddd           dS t          |           \  }}|r|adat                       nt          a|at          |           ddd           dS # 1 swxY w Y   dS )z6Background thread target: download and install tirith.Nr   r+   r   )_install_lockr*   rF   rG   r,   r   r3   r4   rX   r   r   r   r   rH   r   rR   )r   r   r   r   rI   s        r   _background_installr     s    
 ) )%) ) ) ) ) ) ) ) X&& 	"N&(#) ) ) ) ) ) ) ) W\\/"3"3X>>
7>>*%% 	")J*H*H 	'N&(#!) ) ) ) ) ) ) )$ ,FFF	6 	)&N&(#!####,N&,# (((5) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )s$   	DDA/DA DDDc                    t                      }|d         sdS t          Wt          t          urIt          }t          j                            |          r!t          j        |t          j                  r|S dS |d         }t          |          }t          j        	                    |          }|rgt          j                            |          r#t          j        |t          j                  r|a|S t          j        |          }|r|a|S t          adadS t          j        d          }|r|adat                       |S t          j                            t                      d          }t          j                            |          r3t          j        |t          j                  r|adat                       |S t          t          u r4t          dk    r't          j        d          rdadat                       ndS t!                      }|t#                      rt          a|adS t$          t$                                          s7t)          j        t,          d	| id
          at$                                           dS )a  Ensure tirith is available, downloading in background if needed.

    Quick PATH/local checks are synchronous; network download runs in a
    daemon thread so startup never blocks. Safe to call multiple times.
    Returns the resolved path immediately if available, or None.
    r   Nr   r   r   r+   rD   rE   r   T)r   kwargsdaemon)r)   r*   r   r   r3   r   r   r   r   r   rF   rG   r,   rH   r4   rX   rB   rJ   r-   r   	threadingThreadr   start)	r   r(   r3   r   r   r   r   r   r   s	            r   ensure_installedr     so     
!
!C  t !nO&K&K7>>$ 	BIdBG$<$< 	Kt-(O 11Hw!!/22H  
7>>(## 		(BG(D(D 	%NOX&& 	"NL("9t L""E "$o//::J	w~~j!! bi
BG&D&D #"$ (("&6666<;Q;Q6!N&(#!####4
 '((K#=#?#?("-t o&>&>&@&@#*&"L1
 
 

 	4r   2   i  commandc           
      x   t                      }|d         sdg ddS t          |d                   }|d         }|d         }|(t                              d	           |rdg d
dS dg ddS 	 t	          j        |dddddd| gdd|          }n# t          $ r>}t                              d|           |rdg d| dcY d}~S dg d| dcY d}~S d}~wt          j        $ r2 t                              d|           |rdg d| ddcY S dg ddcY S w xY w|j        }|dk    rd}nC|dk    rd}n:|dk    rd}n1t                              d |           |r
dg d!| d"dS dg d!| d#dS g }	d}
	 |j	        
                                rt          j        |j	                  ni }|                    d$g           }|dt                   }	|                    d%d          pddt                   }
nG# t          j        t"          f$ r. t                              d&           |dk    rd'}
n|dk    rd(}
Y nw xY w||	|
dS ))a@  Run tirith security scan on a command.

    Exit code determines action (0=allow, 1=block, 2=warn). JSON enriches
    findings/summary. Spawn failures and timeouts respect fail_open config.
    Programming errors propagate.

    Returns:
        {"action": "allow"|"warn"|"block", "findings": [...], "summary": str}
    r   allowr+   )actionfindingssummaryr   r   r   Nz/tirith path resolved to None; scanning disabledztirith path unavailableblockz%tirith path unavailable (fail-closed)checkz--jsonz--non-interactivez--shellposixz--Tr   ztirith spawn failed: %sztirith unavailable: z#tirith spawn failed (fail-closed): ztirith timed out after %dsztirith timed out (zs)ztirith timed out (fail-closed)r   r   r   warnz'tirith returned unexpected exit code %dztirith exit code z (fail-open)z (fail-closed)r   r   z.tirith JSON parse failed, using exit code onlyz-security issue detected (details unavailable)z/security warning detected (details unavailable))r)   r   r   r   r   r   r>   r   r   stdoutr=   jsonloadsr%   _MAX_FINDINGS_MAX_SUMMARY_LENJSONDecodeErrorAttributeErrorr   )r   r(   r   rk   	fail_openr   r   	exit_coder   r   r   dataraw_findingss                r   check_command_securityr  g  sd     
!
!C  B!rbAAA&s='9::K"#G&'IHIII 	]%2B[\\\!r>efff`'8-@w0
 
 
  k k k0#666 	`%2B^Y\B^B^________!r>idg>i>ijjjjjjjj$ ` ` `3W=== 	d%2BbW^BbBbBbccccc!r>^_____	` !IA~~	a	a 	@)LLL 	o%2BmV_BmBmBmnnn!r>kR[>k>k>klll HGH,2M,?,?,A,AItz&-(((rxx
B///88Ir**0b2C3C2CD .1 H H HEFFFWEGGvGGH (wGGGsJ   - B 
D%C=DCD9DDD0A>G/ /AH32H3)r+   )rh   );__doc__r   r   loggingr   rb   rF   r   r   r   r   r   r9   urllib.requestrp   hermes_constantsr   	getLogger__name__r   r   r   r   r/   boolr   r   r   dictr)   r*   __annotations__r   r,   Lockr   r-   r   r:   r1   r5   rB   rJ   rR   rH   rX   rg   ry   r   r   tupler   r   r   r   r   r   r   r  r0   r   r   <module>r     sO    ,    				                   , , , , , ,		8	$	$ gfff >/3 / /$ / / / /#      t    8 %)d
T! ( ( (!  ! ! ! 	  +/!D( / / / "# " " " "
Fc F F F F
cDj    "D    "     "      d
    .$ $ $3 $ $ $ $ $%3 %# %# %$QU+ % % % %P3  3 SW    2 -1 h2 h2 h2T h2U3:s?5K h2 h2 h2 h2V's 't ' ' ' '
`# `# ` ` ` `F 15 ) ) ) ) ) ) )@ .2 O O Od O O O Ol  LHC LHD LH LH LH LH LH LHr   