+
    iL                     @  a  RO tC0 t R t^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	H
t
 ]P                  ! ]4      t]P                  ! RRR7      t] ^ k R R ltR R	 ltRR
 R lltRtRtR] R] R2t. RPNRQNRRNRSNRTNRUNRVNRWNRXNRYNRZNR[NR\NR]NR^NR_NR`NRaNRbNRcNRdNReNR] 2R3NR] 2R3NRfNRgNRhNRiNRjNRkNRlNRmNRnNtR R lt/ t] ^k ] F`  w  tt]! ]4      t]t]P;                  ]]! 4       4      P?                  ]]04       ]P;                  ]]! 4       4      P?                  ]]04       Kb  	  R R lt R R lt!R R lt"]PF                  ! 4       t$/ t%] ^k / t&] ^k ]! 4       t'] ^k  ! R R4      t(/ t)] ^k / t*] ^k R R  lt+R! R" lt,RoR# R$ llt-R% R& lt.R' R( lt/R) R* lt0R+ R, lt1R- R. lt2R/ R0 lt3R1 R2 lt4R3 R4 lt5R5 R6 lt6R7 R8 lt7R9 R: lt8R; R< lt9RpR= R> llt:R? R@ lt;RA RB lt<RC RD lt=RE RF lt>RG RH lt?RqRI RJ llt@RK RL ltARqRM RN lltBR# )ra  Dangerous command approval -- detection, prompting, and per-session state.

This module is the single source of truth for the dangerous command system:
- Pattern detection (DANGEROUS_PATTERNS, detect_dangerous_command)
- Per-session approval state (thread-safe, keyed by session_key)
- Approval prompting (CLI interactive + gateway async)
- Smart approval via auxiliary LLM (auto-approve low-risk commands)
- Permanent allowlist persistence (config.yaml)
N)Optionalapproval_session_key )defaultc                Z    V ^8  d   QhR\         R\        P                  \         ,          /#    session_keyreturn)strcontextvarsToken)formats   "+/home/ubuntu/hermes-agent/tools/approval.py__annotate__r       s%     8 8 81B1B31G 8    c                @    \         P                  T ;'       g    R4      # )z<Bind the active approval session key to the current context.r   )_approval_session_keysetr	   s   &r   set_current_session_keyr       s     $$[%6%6B77r   c                R    V ^8  d   QhR\         P                  \        ,          RR/# )r   tokenr
   N)r   r   r   )r   s   "r   r   r   %   s%     ' '[%6%6s%; ' 'r   c                0    \         P                  V 4       R# )z/Restore the prior approval session key context.N)r   reset)r   s   &r   reset_current_session_keyr   %   s    &r   c                0    V ^8  d   QhR\         R\         /# )r   r   r
   r   )r   s   "r   r   r   *   s     4 4S 4 4r   c                l    \         P                  4       pV'       d   V# \        P                  ! RV 4      # )z>Return the active session key, preferring context-local state.HERMES_SESSION_KEY)r   getosgetenv)r   r	   s   & r   get_current_session_keyr#   *   s+    '++-K99)733r   z$(?:~|\$home|\$\{home\})/\.ssh(?:/|$)z\(?:~\/\.hermes/|(?:\$home|\$\{home\})/\.hermes/|(?:\$hermes_home|\$\{hermes_home\})/)\.env\bz(?:/etc/|/dev/sd||)z\btee\b.*["\']?zoverwrite system file via teez>>?\s*["\']?z%overwrite system file via redirectionc                0    V ^8  d   QhR\         R\         /# )r   patternr
   r   )r   s   "r   r   r   m   s     I I I Ir   c                R    RV 9   d   V P                  R4      ^,          # V R,          # )zIReproduce the old regex-derived approval key for backwards compatibility.z\b:N   N)split)r'   s   &r   _legacy_pattern_keyr+   m   s&    &+w&67=="HGCLHr   c                F    V ^8  d   QhR\         R\        \         ,          /# )r   pattern_keyr
   )r   r   )r   s   "r   r   r   z   s"     @ @s @s3x @r   c                .    \         P                  W 04      # )zReturn all approval keys that should match this pattern.

New approvals use the human-readable description string, but older
command_allowlist entries and session approvals may still contain the
historical regex-derived key.
)_PATTERN_KEY_ALIASESr    r-   s   &r   _approval_key_aliasesr1   z   s      ##K??r   c                0    V ^8  d   QhR\         R\         /# r   commandr
   r   )r   s   "r   r   r      s      c c r   c                t    ^ RI Hp V! V 4      p V P                  RR4      p \        P                  ! RV 4      p V # )a  Normalize a command string before dangerous-pattern matching.

Strips ANSI escape sequences (full ECMA-48 via tools.ansi_strip),
null bytes, and normalizes Unicode fullwidth characters so that
obfuscation techniques cannot bypass the pattern-based detection.
)
strip_ansi r   NFKC)tools.ansi_stripr6   replaceunicodedata	normalize)r4   r6   s   & r    _normalize_command_for_detectionr=      s:     , !Goofb)G##FG4GNr   c                0    V ^8  d   QhR\         R\        /# r3   )r   tuple)r   s   "r   r   r      s      c e r   c                    \        V 4      P                  4       p\         FM  w  r#\        P                  ! W!\        P
                  \        P                  ,          4      '       g   KG  TpRWC3u # 	  R# )zCheck if a command matches any dangerous patterns.

Returns:
    (is_dangerous, pattern_key, description) or (False, None, None)
T)FNN)r=   lowerDANGEROUS_PATTERNSresearch
IGNORECASEDOTALL)r4   command_lowerr'   descriptionr-   s   &    r   detect_dangerous_commandrI      sZ     5W=CCEM 299WR]]RYY-FGG%K+33 !3 r   c                   :   a  ] tR t^t o RtRtV 3R lR ltRtV tR# )_ApprovalEntryz@One pending dangerous-command approval inside a gateway session.c                    < V ^8  d   QhRS[ /# )r   datadict)r   __classdict__s   "r   r   _ApprovalEntry.__annotate__   s     * *T *r   c                T    \         P                  ! 4       V n        Wn        R V n        R # N)	threadingEventeventrM   result)selfrM   s   &&r   __init___ApprovalEntry.__init__   s    __&
	%)r   )rM   rV   rW   N)rV   rM   rW   )	__name__
__module____qualname____firstlineno____doc__	__slots__rY   __static_attributes____classdictcell__)rP   s   @r   rK   rK      s     J+I* *r   rK   c                (    V ^8  d   QhR\         RR/# r   r	   r
   Nr   )r   s   "r   r   r      s     	. 	. 	.T 	.r   c                n    \         ;_uu_ 4        V\        V &   RRR4       R#   + '       g   i     R# ; i)uM  Register a per-session callback for sending approval requests to the user.

The callback signature is ``cb(approval_data: dict) -> None`` where
*approval_data* contains ``command``, ``description``, and
``pattern_keys``.  The callback bridges sync→async (runs in the agent
thread, must schedule the actual send on the event loop).
N)_lock_gateway_notify_cbs)r	   cbs   &&r   register_gateway_notifyri      s      
+-K( 
   
#4	c                (    V ^8  d   QhR\         RR/# rd   r   )r   s   "r   r   r      s     
 
3 
4 
r   c                    \         ;_uu_ 4        \        P                  V R4       \        P                  V . 4      pV F  pVP                  P                  4        K  	  RRR4       R#   + '       g   i     R# ; i)zUnregister the per-session gateway approval callback.

Signals ALL blocked threads for this session so they don't hang forever
(e.g. when the agent run finishes or is interrupted).
N)rf   rg   pop_gateway_queuesrV   r   r	   entriesentrys   &  r   unregister_gateway_notifyrr      sO     
T2!%%k26EKKOO  
s   AA))A:	c                H    V ^8  d   QhR\         R\         R\        R\        /# )r   r	   choiceresolve_allr
   )r   boolint)r   s   "r   r   r      s*      # s *.;>r   c                   \         ;_uu_ 4        \        P                  V 4      pV'       g    RRR4       ^ # V'       d   \        V4      pVP	                  4        MVP                  ^ 4      .pV'       g   \        P                  V R4       RRR4       X F#  pWn        VP                  P                  4        K%  	  \        V4      #   + '       g   i     LD; i)a<  Called by the gateway's /approve or /deny handler to unblock
waiting agent thread(s).

When *resolve_all* is True every pending approval in the session is
resolved at once (``/approve all``).  Otherwise only the oldest one
is resolved (FIFO).

Returns the number of approvals resolved (0 means nothing was pending).
N)
rf   rn   r    listclearrm   rW   rV   r   len)r	   rt   ru   queuetargetsrq   s   &&&   r   resolve_gateway_approvalr~      s     
##K0 
 5kGKKMyy|nGT2 
   w< 
s   CC 5C6CC	c                0    V ^8  d   QhR\         R\        /# r   r   rv   )r   s   "r   r   r      s     6 6s 6t 6r   c                    \         ;_uu_ 4        \        \        P                  V 4      4      uuRRR4       #   + '       g   i     R# ; i)zFCheck if a session has one or more blocking gateway approvals waiting.N)rf   rv   rn   r    r   s   &r   has_blocking_approvalr      s&    	O''45 
s	   8A		c                0    V ^8  d   QhR\         R\        /# r   )r   rw   )r   s   "r   r   r     s     9 9 9 9r   c                    \         ;_uu_ 4        \        \        P                  V . 4      4      uuRRR4       #   + '       g   i     R# ; i)z>Return the number of pending blocking approvals for a session.N)rf   r{   rn   r    r   s   &r   pending_approval_countr     s(    	?&&{B78 
s	   9A
	c                0    V ^8  d   QhR\         R\        /# )r   r	   approvalr   rO   )r   s   "r   r   r   
  s     ) ) )t )r   c                n    \         ;_uu_ 4        V\        V &   RRR4       R#   + '       g   i     R# ; i)z/Store a pending approval request for a session.Nrf   _pending)r	   r   s   &&r   submit_pendingr   
  s    	 ( 
rj   c                F    V ^8  d   QhR\         R\        \        ,          /# r   )r   r   rO   )r   s   "r   r   r     s     / /S /Xd^ /r   c                    \         ;_uu_ 4        \        P                  V R4      uuRRR4       #   + '       g   i     R# ; i)z5Retrieve and remove a pending approval for a session.N)rf   r   rm   r   s   &r   pop_pendingr     s!    	||K. 
s	   0A	c                0    V ^8  d   QhR\         R\        /# r   r   )r   s   "r   r   r     s     ' 'S 'T 'r   c                n    \         ;_uu_ 4        V \        9   uuRRR4       #   + '       g   i     R# ; i)z2Check if a session has a pending approval request.Nr   r   s   &r   has_pendingr     s    	h& 
s   	#4	c                0    V ^8  d   QhR\         R\         /# )r   r	   r-   r   )r   s   "r   r   r     s     J J J3 Jr   c                    \         ;_uu_ 4        \        P                  V \        4       4      P	                  V4       RRR4       R#   + '       g   i     R# ; i)z(Approve a pattern for this session only.N)rf   _session_approved
setdefaultr   add)r	   r-   s   &&r   approve_sessionr     s0    	$$[#%8<<[I 
s   .AA	c                <    V ^8  d   QhR\         R\         R\        /# )r   r	   r-   r
   r   )r   s   "r   r   r   "  s&     D DS Ds Dt Dr   c                  a \        V4      p\        ;_uu_ 4        \        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      '       d    RRR4       R# \        P	                  V \        4       4      o\        ;QJ d    V3R lV 4       F  '       g   K   RM	  RM! V3R lV 4       4      uuRRR4       #   + '       g   i     R# ; i)zCheck if a pattern is approved (session-scoped or permanent).

Accept both the current canonical key and the legacy regex-derived key so
existing command_allowlist entries continue to work after key migrations.
c              3   2   "   T F  q\         9   x  K  	  R # 5irS   )_permanent_approved).0aliass   & r   	<genexpr>is_approved.<locals>.<genexpr>*  s     A++s   TFNc              3   ,   <"   T F	  qS9   x  K  	  R # 5irS    )r   r   session_approvalss   & r   r   r   -  s     C7%--7s   )r1   rf   anyr   r    r   )r	   r-   aliasesr   s   && @r   is_approvedr   "  s     $K0G	3AA333AAAA 
 .11+suEsC7CsssC7CC	 
s"   CCC!=C!CC	c                $    V ^8  d   QhR\         /# )r   r-   r   )r   s   "r   r   r   0  s     - -3 -r   c                    \         ;_uu_ 4        \        P                  V 4       RRR4       R#   + '       g   i     R# ; i)z)Add a pattern to the permanent allowlist.N)rf   r   r   r0   s   &r   approve_permanentr   0  s!    	, 
	   /A 	c                $    V ^8  d   QhR\         /# r   patternsr   )r   s   "r   r   r   6  s     - -S -r   c                    \         ;_uu_ 4        \        P                  V 4       RRR4       R#   + '       g   i     R# ; i)z2Bulk-load permanent allowlist entries from config.N)rf   r   update)r   s   &r   load_permanentr   6  s!    	""8, 
r   c                $    V ^8  d   QhR\         /# )r   r	   r   )r   s   "r   r   r   <  s     	 	s 	r   c                R   \         ;_uu_ 4        \        P                  V R4       \        P                  V R4       \        P                  V R4       \
        P                  V . 4      pV F  pVP                  P                  4        K  	  RRR4       R#   + '       g   i     R# ; i)z7Clear all approvals and pending requests for a session.N)rf   r   rm   r   rg   rn   rV   r   ro   s   &  r   clear_sessionr   <  sk    	k40[$'T2!%%k26EKKOO  
s   A<BB&	c                $    V ^8  d   QhR\         /# r   r
   r   )r   s   "r   r   r   L  s      # r   c                      ^ RI Hp  V ! 4       p\        VP                  R. 4      ;'       g    . 4      pV'       d   \	        V4       V#   \
         d    \        4       u # i ; i)zLoad permanently allowed command patterns from config.

Also syncs them into the approval module so is_approved() works for
patterns added via 'always' in a previous session.
load_configcommand_allowlist)hermes_cli.configr   r   r    r   	Exception)r   configr   s      r   load_permanent_allowlistr   L  sU    1vzz"5r:@@bA8$ us   *A	 A	 A	 	A! A!c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   r   ]  s     : :s :r   c                     ^ RI HpHp V! 4       p\        V 4      VR&   V! V4       R#   \         d"   p\
        P                  RT4        Rp?R# Rp?ii ; i)z4Save permanently allowed command patterns to config.)r   save_configr   zCould not save allowlist: %sN)r   r   r   ry   r   loggerwarning)r   r   r   r   es   &    r   save_permanent_allowlistr   ]  sJ    :>&*8n"#F :5q99:s   %) AAAc          
      b    V ^8  d   QhR\         R\         R\        R,          R\        R\         /# )r   r4   rH   timeout_secondsNallow_permanentr
   )r   rw   rv   )r   s   "r   r   r   l  s?     Q Qs Q Q/2TzQ/3Q :=Qr   c                V  aa Vf   \        4       pVe    V! WSR7      # R\        P                  R&     \	        4        \	        RV 24       \	        RV  24       \	        4        S'       d   \	        R4       M\	        R	4       \	        4        \
        P                  P                  4        R
R/oVV3R lp\        P                  ! VRR7      pVP                  4        VP                  VR7       VP                  4       '       d]   \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R# SR
,          pVR9   d]   \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R# VR9   d]   \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R# VR9   d   S'       g]   \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R# \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R# \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R#   \         d     R# i ; i  \        \        3 d^    \	        R4        R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        R# i ; i  R\        P                  9   d   \        P                  R \	        4        \
        P                  P                  4        i ; i)a  Prompt the user to approve a dangerous command (CLI only).

Args:
    allow_permanent: When False, hide the [a]lways option (used when
        tirith warnings are present, since broad permanent allowlisting
        is inappropriate for content-level security findings).
    approval_callback: Optional callback registered by the CLI for
        prompt_toolkit integration. Signature:
        (command, description, *, allow_permanent=True) -> str.

Returns: 'once', 'session', 'always', or 'deny'
)r   deny1HERMES_SPINNER_PAUSETu     ⚠️  DANGEROUS COMMAND: z      z2      [o]nce  |  [s]ession  |  [a]lways  |  [d]enyz%      [o]nce  |  [s]ession  |  [d]enyrt   r   c                     <  S'       d   R MRp \        V 4      P                  4       P                  4       SR&   R#   \        \        3 d
    RSR&    R# i ; i)z      Choice [o/s/a/D]: z      Choice [o/s/D]: rt   r   N)inputstriprA   EOFErrorOSError)promptr   rW   s    r   	get_input,prompt_dangerous_approval.<locals>.get_input  sQ    *;J7PhF',V}':':'<'B'B'DF8$ '* *')F8$*s   ; .; AA)targetdaemontimeoutu$   
      ⏱ Timeout - denying commandonceu         ✓ Allowed oncesessionu"         ✓ Allowed for this sessionalwaysu&         ✓ Added to permanent allowlistu         ✗ Deniedu   
      ✗ Cancelled)or   )sr   )ar   )_get_approval_timeoutr   r!   environprintsysstdoutflushrT   Threadstartjoinis_aliver   KeyboardInterrupt)	r4   rH   r   r   approval_callbackr   threadrt   rW   s	   &&&f&   @r   prompt_dangerous_approvalr   l  s     /1$	$W5DF F
 *-BJJ%&6G1+?@F7)$%GJK=>GJJ^F* %%YtDFLLNKKK0  =>. "RZZ/

12

1 H%F&./$ "RZZ/

12

) ++:;  "RZZ/

12

# ?*&>?$ "RZZ/

12

 >? "RZZ/

12

 () "RZZ/

12

u  		f '( %&!RZZ/

12

 "RZZ/

12

sZ   	M CM' #M' M' 2M' M' M' 9M' M$#M$'OO OO AP(c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   r     s      c r   c                    \        V \        4      '       d   V RJ d   R# R# \        V \        4      '       d+   V P                  4       P	                  4       pT;'       g    R# R# )a  Normalize approval mode values loaded from YAML/config.

  YAML 1.1 treats bare words like `off` as booleans, so a config entry like
  `approvals:
mode: off` is parsed as False unless quoted. Treat that as the
  intended string mode instead of falling back to manual approvals.
  Foffmanual)
isinstancerv   r   r   rA   )mode
normalizeds   & r   _normalize_approval_moder     sT     $u383$ZZ\'')
%%X%r   c                $    V ^8  d   QhR\         /# r   rN   )r   s   "r   r   r     s      d r   c                 |     ^ RI Hp  V ! 4       pVP                  R/ 4      ;'       g    / #   \         d    / u # i ; i)zLRead the approvals config block. Returns a dict with 'mode', 'timeout', etc.r   	approvals)r   r   r    r   )r   r   s     r   _get_approval_configr     s>    1zz+r*00b0 	s   %+ + ;;c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   r     s     * *C *r   c                 L    \        4       P                  RR4      p \        V 4      # )zHRead the approval mode from config. Returns 'manual', 'smart', or 'off'.r   r   )r   r    r   )r   s    r   _get_approval_moder     s"    !%%fh7D#D))r   c                $    V ^8  d   QhR\         /# r   )rw   )r   s   "r   r   r     s      s r   c                 z     \        \        4       P                  R^<4      4      #   \        \        3 d     ^<# i ; i)z>Read the approval timeout from config. Defaults to 60 seconds.r   )rw   r   r    
ValueError	TypeErrorr   r   r   r   r     s:    ')--i<==	" s   "% ::c                <    V ^8  d   QhR\         R\         R\         /# )r   r4   rH   r
   r   )r   s   "r   r   r     s!     1 1C 1c 1c 1r   c           
         ^ RI HpHp V! RR7      w  rEV'       d	   V'       g   \        P	                  R4       R# RV  RV R2pVP
                  P                  P                  ! RR	VR
RRRV/./V! ^4      BR^ /B pVP                  ^ ,          P                  P                  ;'       g    RP                  4       P                  4       pRV9   d   R# RV9   d   R# R#   \         d"   p	\        P	                  RT	4        Rp	?	R# Rp	?	ii ; i)a  Use the auxiliary LLM to assess risk and decide approval.

Returns 'approve' if the LLM determines the command is safe,
'deny' if genuinely dangerous, or 'escalate' if uncertain.

Inspired by OpenAI Codex's Smart Approvals guardian subagent
(openai/codex#13860).
)get_text_auxiliary_clientauxiliary_max_tokens_paramr   )taskz4Smart approvals: no aux client available, escalatingescalatezYou are a security reviewer for an AI coding agent. A terminal command was flagged by pattern matching as potentially dangerous.

Command: z
Flagged reason: ul  

Assess the ACTUAL risk of this command. Many flagged commands are false positives — for example, `python -c "print('hello')"` is flagged as "script execution via -c flag" but is completely harmless.

Rules:
- APPROVE if the command is clearly safe (benign script execution, safe file operations, development tools, package installs, git operations, etc.)
- DENY if the command could genuinely damage the system (recursive delete of important paths, overwriting system files, fork bombs, wiping disks, dropping databases, etc.)
- ESCALATE if you're uncertain

Respond with exactly one word: APPROVE, DENY, or ESCALATEmodelmessagesroleusercontenttemperaturer   APPROVEapproveDENYr   z1Smart approvals: LLM call failed (%s), escalatingNr   )agent.auxiliary_clientr  r  r   debugchatcompletionscreatechoicesmessager
  r   upperr   )
r4   rH   r  r  clientr  r   responseanswerr   s
   &&        r   _smart_approver    s   (`1zBULLOP

  	:= ;;**11 

vy&9:
 ),
 	
 ""1%--55;;BBDJJLv H!Ls.   "C C A/C ,&C C D*DDc                <    V ^8  d   QhR\         R\         R\        /# r   r4   env_typer
   r   )r   s   "r   r   r     s'     H/ H/S H/C H/7;H/r   c                   VR9   d   RRRR/# \         P                  ! R4      '       d   RRRR/# \        V 4      w  r4pV'       g   RRRR/# \        4       p\	        Wd4      '       d   RRRR/# \         P                  ! R4      p\         P                  ! R4      pV'       g   V'       g   RRRR/# V'       g   \         P                  ! R4      '       d(   \        VR	V R
VRV/4       RRR
VRRR	V RVRRV RV  R2/# \        WVR7      p	V	R8X  d   RRRRV R2R
VRV/# V	R8X  d   \        Wd4       M,V	R8X  d&   \        Wd4       \        V4       \        \        4       RRRR/# )a  Check if a command is dangerous and handle approval.

This is the main entry point called by terminal_tool before executing
any command. It orchestrates detection, session checks, and prompting.

Args:
    command: The shell command to check.
    env_type: Terminal backend type ('local', 'ssh', 'docker', etc.).
    approval_callback: Optional CLI callback for interactive prompts.

Returns:
    {"approved": True/False, "message": str or None, ...}
approvedTr  NHERMES_YOLO_MODEHERMES_INTERACTIVEHERMES_GATEWAY_SESSIONHERMES_EXEC_ASKr4   r-   rH   Fstatusapproval_requiredu.   ⚠️ This command is potentially dangerous (z3). Asking the user for approval.

**Command:**
```

```)r   r   zBBLOCKED: User denied this potentially dangerous command (matched 'zL' pattern). Do NOT retry this command - the user has explicitly rejected it.r   r   dockersingularitymodaldaytona)r!   r"   rI   r#   r   r   r   r   r   r   r   )
r4   r  r   is_dangerousr-   rH   r	   is_cli
is_gatewayrt   s
   &&&       r   check_dangerous_commandr/    s    @@D)T22 
yy#$$D)T22-Eg-N*L{D)T22)+K;,,D)T22YY+,F34J*D)T22RYY011{w;;%
 	 ;)w;@ NGGNiuV

 
	
 'w9JLF [\g[h  iu  v;;	
 	
 1	8	1+& !45i..r   c                0    V ^8  d   QhR\         R\        /# )r   tirith_resultr
   )rO   r   )r   s   "r   r   r   j  s     3 3d 3s 3r   c           	     V   V P                  R4      ;'       g    . pV'       g!   V P                  R4      ;'       g    RpRV 2# . pV F  pVP                  RR4      pVP                  RR4      pVP                  RR4      pV'       d4   V'       d,   TP                  V'       d   R	V R
V RV 2MV RV 24       Kt  V'       g   K~  TP                  V'       d	   R	V R
V 2MT4       K  	  V'       g!   V P                  R4      ;'       g    RpRV 2# RRP                  V4      ,           # )zBuild a human-readable description from tirith findings.

Includes severity, title, and description for each finding so users
can make an informed approval decision.
findingssummaryzsecurity issue detectedzSecurity scan: severityr   titlerH   [z] z: u   Security scan — ; )r    appendr   )r1  r3  r4  partsfr5  r6  descs   &       r   _format_tirith_descriptionr=  j  s      ,22H##I.KK2K 	**E55R(gr"uu]B'TLLH1XJbr$8UGSUVZU[J\]ULLH1XJb0%H  ##I.KK2K 	**$))E"222r   c                <    V ^8  d   QhR\         R\         R\        /# r  r   )r   s   "r   r   r     s,     dA dAc dAS dA8<dAr   c                   VR99   d   RRRR/# \        4       p\        P                  ! R4      '       g   VR8X  d   RRRR/# \        P                  ! R4      p\        P                  ! R4      p\        P                  ! R	4      pV'       g   V'       g   V'       g   RRRR/# R
RR. RR/p ^ RIHp V! V 4      p\        V 4      w  rp. p\        4       pVR
,          R:9   du   VP                  R4      ;'       g    . pV'       d   V^ ,          P                  RR4      MRpRV 2p\        V4      p\        VV4      '       g   VP                  VVR34       V	'       d%   \        W4      '       g   VP                  WR34       V'       g   RRRR/# VR8X  d   RP                  R V 4       4      p\        V V4      pVR8X  dB   V F  w  p p\        VV4       K  	  \        P!                  RV R,          V4       RRRRRRRV/# VR8X  d%   RP                  R V 4       4      pRRRRV R2R R/# RP                  R! V 4       4      pV^ ,          ^ ,          pV UUu. uF	  w  p pVNK  	  ppp\"        ;QJ d    R" V 4       F  '       g   K   RM	  RM! R" V 4       4      pV'       g
   V'       Ed   Rp\$        ;_uu_ 4        \&        P                  V4      pRRR4       VEe   R#V R$VR%VRV/p\)        V4      p\$        ;_uu_ 4        \*        P-                  V. 4      P                  V4       RRR4        V! V4       \7        4       P                  R(R)4      p \9        T4      pTP>                  PA                  TR*7      p \$        ;_uu_ 4        \*        P                  T. 4      pTT9   d   TP3                  T4       T'       g   \*        P5                  TR4       RRR4       TPB                  p!T '       d   T!e   T!R8X  d   T '       g   R+MR,p"RRRR-T" R.2R$TRT/# T FZ  w  ppp#T!R/8X  g   T!R08X  d   T#'       d   \        TT4       K+  T!R08X  g   K4  \        TT4       \E        T4       \G        \H        4       K\  	  RRRRR1RRT/# \K        VR#V R$VR%VRV/4       RRR$VR2R3R#V RVRR4V R5V  R62/# \M        V VV'       * VR77      p!V!R8X  d   RRRR8R$VRV/# V FZ  w  ppp#V!R/8X  g   V!R08X  d   V#'       d   \        VV4       K+  V!R08X  g   K4  \        VV4       \E        V4       \G        \H        4       K\  	  RRRRR1RRV/#   \
         d     ELi ; iu uppi   + '       g   i     EL; i  + '       g   i     ELF; i  \.         d   p\        P1                  R&T4       \$        ;_uu_ 4        \*        P                  T. 4      pTT9   d   TP3                  T4       T'       g   \*        P5                  TR4       RRR4       M  + '       g   i     M; iRRRR'R$TRT/u Rp?# Rp?ii ; i  \:        \<        3 d    R)p ELi ; i  + '       g   i     ELu; i);a/  Run all pre-exec security checks and return a single approval decision.

Gathers findings from tirith and dangerous-command detection, then
presents them as a single combined approval request. This prevents
a gateway force=True replay from bypassing one check when only the
other was shown to the user.
r  Tr  Nr   r   r!  r"  r#  actionallowr3  r4  r   )check_command_securityrule_idunknownztirith:Fsmartr8  c              3   *   "   T F	  w  rqx  K  	  R # 5irS   r   r   _r<  s   &  r   r   +check_all_command_guards.<locals>.<genexpr>  s     )J:1A$   r  z'Smart approval: auto-approved '%s' (%s):N<   Nsmart_approvedrH   r   c              3   *   "   T F	  w  rqx  K  	  R # 5irS   r   rG  s   &  r   r   rI    s     -NXzqdXrJ  zBLOCKED by smart approval: z@. The command was assessed as genuinely dangerous. Do NOT retry.smart_deniedc              3   *   "   T F	  w  rqx  K  	  R # 5irS   r   rG  s   &  r   r   rI    s     >XzqdXrJ  c              3   ,   "   T F
  w   rVx  K  	  R # 5irS   r   )r   rH  is_ts   &  r   r   rI    s     5HjaTHs   r4   r-   pattern_keysz"Gateway approval notify failed: %sz?BLOCKED: Failed to send approval request to user. Do NOT retry.gateway_timeouti,  r   z	timed outzdenied by userzBLOCKED: Command z. Do NOT retry this command.r   r   user_approvedr$  r%  u   ⚠️ z2. Asking the user for approval.

**Command:**
```
r&  )r   r   z#BLOCKED: User denied. Do NOT retry.r'  )blockwarn)'r   r!   r"   tools.tirith_securityrB  ImportErrorrI   r#   r    r=  r   r9  r   r  r   r   r  r   rf   rg   rK   rn   r   r   r   removerm   r   rw   r   r   rV   waitrW   r   r   r   r   r   )$r4   r  r   approval_moder-  r.  is_askr1  rB  r,  r-   rH   warningsr	   r3  rC  
tirith_keytirith_desccombined_desc_for_llmverdictkeyrH  combined_descprimary_keyall_keys
has_tirith	notify_cbapproval_datarq   excr|   r   resolvedrt   reason	is_tiriths$   &&&                                 r   check_all_command_guardsrm    sm    @@D)T22 '(M	yy#$$(>D)T22YY+,F34JYY()F *VD)T22 w
B	2FM@.w7
 .Fg-N*L{
 H)+K X"33 $$Z066B;C(1+//)Y7wi(
0?;
33OOZd;<;44OO[u=> D)T22  $		)J)J J *?@i%	QS1 &LLB '<>i$d!#8: : $(II-NX-N$N!E89N8O P\ \	  II>X>>M1+a.K%-.X	QXH.5H55H55J VV	U+//<I   
 7{}	M #=1E**;;BB5I -(" +,001BCHGg, {{'''8H '++K<E>LL'#''T:  \\Fv~61A,4:J!26(:VW!;!=	  &.!Q	Y&6X+=)#K5x'#K5%c*,-@A &. i#T=-I I
 	{w;H=	%
 	 ;)w=-(^_f^gglm	
 		
 'w;E~9JLF <;=	
 	
 &Q	Y6X#5)K-xK-c"$%89 & iT=-A AA  @ / UU   CSIU+//R@E~U+ '++K> UUU `!;!=	 & 	* 
 s   *T= U U&U)5U= X, 6YY=UUU&	)U:	=X)$X$,6X#X9	X$XX$X)$X),YYY	c                   V ^8  d   Qh/ ^ \         9   d   \        P                  \        ,          ;R&   ^\         9   d&   \        \        \
        \        ,          3,          ;R&   ^\         9   d   \        \        \        3,          ;R&   ^\         9   d   \        \        \
        3,          ;R&   ^\         9   d
   \
        ;R&   ^\         9   d   \        \        \        3,          ;R&   ^\         9   d   \        \        \        3,          ;R&   # )r   r   r/   r   r   r   rn   rg   )__conditional_annotations__r   
ContextVarr   rO   r   ry   object)r   s   "r   r   r      s      2 {--c2 3b / .d3C=) .cX  $sDy/ YZ ' &4S> &[\ !  S  ]F & %c4i %GH , +T#v+& +Ir   )z\brm\s+(-[^\s]*\s+)*/zdelete in root path)z\brm\s+-[^\s]*rzrecursive delete)z\brm\s+--recursive\bzrecursive delete (long flag))z8\bchmod\s+(-[^\s]*\s+)*(777|666|o\+[rwx]*w|a\+[rwx]*w)\bz world/other-writable permissions)z8\bchmod\s+--recursive\b.*(777|666|o\+[rwx]*w|a\+[rwx]*w)z*recursive world/other-writable (long flag))z\bchown\s+(-[^\s]*)?R\s+rootzrecursive chown to root)z\bchown\s+--recursive\b.*rootz#recursive chown to root (long flag))z\bmkfs\bzformat filesystem)z\bdd\s+.*if=z	disk copy)z>\s*/dev/sdzwrite to block device)z\bDROP\s+(TABLE|DATABASE)\bzSQL DROP)z \bDELETE\s+FROM\b(?!.*\bWHERE\b)zSQL DELETE without WHERE)z\bTRUNCATE\s+(TABLE)?\s*\wzSQL TRUNCATE)z	>\s*/etc/zoverwrite system config)z#\bsystemctl\s+(stop|disable|mask)\bzstop/disable system service)z\bkill\s+-9\s+-1\bzkill all processes)z\bpkill\s+-9\bzforce kill processes)z(:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:z	fork bomb)z%\b(bash|sh|zsh|ksh)\s+-[^\s]*c(\s+|$)zshell command via -c/-lc flag)z)\b(python[23]?|perl|ruby|node)\s+-[ec]\s+zscript execution via -e/-c flag)z\b(curl|wget)\b.*\|\s*(ba)?sh\bzpipe remote content to shell)z1\b(bash|sh|zsh|ksh)\s+<\s*<?\s*\(\s*(curl|wget)\bz.execute remote script via process substitution)z\bxargs\s+.*\brm\bzxargs with rm)z\bfind\b.*-exec\s+(/\S*/)?rm\bzfind -exec rm)z\bfind\b.*-delete\bzfind -delete)z4gateway\s+run\b.*(&\s*$|&\s*;|\bdisown\b|\bsetsid\b)Mstart gateway outside systemd (use 'systemctl --user restart hermes-gateway'))z\bnohup\b.*gateway\s+run\brr  )z1\b(pkill|killall)\b.*\b(hermes|gateway|cli\.py)\bz.kill hermes/gateway process (self-termination))z\b(cp|mv|install)\b.*\s/etc/zcopy/move file into /etc/)z\bsed\s+-[^\s]*i.*\s/etc/zin-place edit of system config)z\bsed\s+--in-place\b.*\s/etc/z*in-place edit of system config (long flag))F)NTNrS   )Dro  r_   r   loggingr!   rC   r   rT   r;   typingr   	getLoggerr[   r   rp  r   r   r   r#   _SSH_SENSITIVE_PATH_HERMES_ENV_PATH_SENSITIVE_WRITE_TARGETrB   r+   r/   _pattern_description_legacy_key_canonical_keyr   r   r   r1   r=   rI   Lockrf   r   r   r   rK   rn   rg   ri   rr   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r/  r=  rm  r   )ro  s   @r   <module>r~     sl     	 	 
   			8	$ 6A5K5K6  8
'
4 >   A &5&,& >& f	&
 p& A& N& '& #& .& 1& F& 4& .& L&  2!&" 0#&$ ?%&( P)&* V+&, I-&. m/&0 	/013RS1&2 	,-.0WX3&4 -5&6 97&8 -9&< O=&> u?&B mC&F CG&H EI&J UK& RI
 -/  .0Hl%h/K!N##NCE:AA>S^B_`##K7>>^?\]	 1@$$ 	 $&  &5   * * $& %)+  +	.
:69)/'JD--	 ":Qh*1hH/^36dA dAr   