
    NQjb                       d Z ddlmZ ddlZddlZddlZddlmZ  eh d          Z	 ej
        dej                  Z ej
        dej                  Z ej
        dej                  Zd	ZddZddZddZddZddZdS )ul  Security checks for user-configured MCP server entries.

MCP stdio transports intentionally support arbitrary local commands so users can
run custom servers. This module does not try to sandbox that capability. It
blocks two high-signal abuse shapes seen in the wild:

1. The exfiltration shape from #45620: a shell interpreter whose inline script
   invokes network egress tooling.
2. The persistence shape from the June 2026 ``hermes-0day`` campaign: a shell
   interpreter whose inline script writes to OS persistence surfaces
   (``~/.ssh/authorized_keys``, ``/etc/ssh``, ``/etc/pam.d``, ``sudoers``,
   crontab, shell rc files). The campaign planted ``command: bash`` MCP entries
   whose payload appended an attacker SSH key to ``authorized_keys``; Hermes
   re-executed them on every cron tick / startup, re-installing the backdoor.

3. A hardcoded indicator-of-compromise (IOC) blocklist for that campaign — the
   attacker's ``hermes-0day`` SSH public key and source IPs. Any entry whose
   command/args/env carry an IOC is refused outright, regardless of shape, so a
   pre-planted ``config.yaml`` cannot spawn it.

These checks run BOTH at save time (``_save_mcp_server`` — dashboard API + CLI)
and at spawn time (``tools.mcp_tool._filter_suspicious_mcp_servers`` — discovery
/ cron / startup), so a hand-edited or pre-planted entry is also caught before
it can execute.
    )annotationsN)Any>   cmd.exepwsh.exepowershell.exeshcmdzshbashdashfishpwsh
powershellz(?<![\w.-])(?:curl|wget|nc|ncat|socat)(?![\w.-])|/dev/tcp/|\bInvoke-WebRequest\b|\bInvoke-RestMethod\b|\bSystem\.Net\.WebClient\bzB\.env\b|--data-binary|--data-raw|\b-X\s+POST\b|\bPOST\b|<\s*[^\s]+zauthorized_keys|\.ssh/|/etc/ssh\b|/etc/pam\.d\b|pam_[\w-]+\.so|/etc/sudoers|/etc/cron|crontab\b|/etc/rc\.local|/etc/systemd|\.bashrc\b|\.bash_profile\b|\.profile\b|\.zshrc\b)7AAAAC3NzaC1lZDI1NTE5AAAAICBoh1oDC4DnsO1m5mJ4yfEKrQebaFhzhermes-0dayz60.165.167.z118.182.244.156z61.178.123.196commandr   returnstrc                ^   t          | pd                                          }|sdS 	 t          j        |t          j        dk              }n$# t          $ r |                                }Y nw xY w|r|d         n|}t          j                            |          	                                S )N nt)posixr   )
r   stripshlexsplitosname
ValueErrorpathbasenamelower)r   textpartsfirsts       ;/home/alina/.hermes/hermes-agent/hermes_cli/mcp_security.py_command_basenamer%   [   s    w}"##%%D rDD:::   

'E!HH4E7E""((***s   $A A/.A/argsc                    | dS t          | t          t          f          rd                    d | D                       S t	          |           S )Nr    c              3  4   K   | ]}t          |          V  d S Nr   ).0items     r$   	<genexpr>z!_inline_script.<locals>.<genexpr>k   s(      33dD		333333    )
isinstancelisttuplejoinr   )r&   s    r$   _inline_scriptr4   g   sO    |r$u&& 4xx33d333333t99r/   entrydict[str, Any]c                   t          |                     d          pd          g}|                    t          |                     d                               |                     d          }t	          |t
                    r1|                    d |                                D                        d                    |          S )zEFlatten command + args + env values into one string for IOC scanning.r   r   r&   envc              3  4   K   | ]}t          |          V  d S r*   r+   )r,   vs     r$   r.   z_entry_text.<locals>.<genexpr>u   s(      22SVV222222r/   r(   )	r   getappendr4   r0   dictextendvaluesr3   )r5   r"   r8   s      r$   _entry_textr@   o   s    EIIi006B778E	LL		& 1 122333
))E

C#t 322SZZ\\22222288E??r/   r   	list[str]c                v   t          |t                    sg S g }t          |          }t          D ]&}||v r |                    d|  d| d           |c S '|                    d          }t          |          }|t          vr|S t          |                    d                    }|s|S t          
                    |          r=d|  d| d}t          
                    |          r|dz  }|                    |           t          
                    |          r|                    d|  d| d	           |S )
a?  Return security warnings for an MCP server entry.

    Empty return means the entry is not suspicious. This is intentionally not a
    whitelist: legitimate local MCPs can still use custom commands, Python
    scripts, npx, uvx, etc. We block three narrow shapes only:

    * a known hermes-0day IOC anywhere in command/args/env (hardcoded blocklist);
    * a shell interpreter whose inline script invokes network egress (#45620);
    * a shell interpreter whose inline script writes to an OS persistence
      surface (June 2026 hermes-0day SSH/PAM/sudoers/cron shape).
    zMCP server 'z9' contains a known hermes-0day indicator-of-compromise ('z')r   r&   z' uses shell interpreter 'z' with network egress in argsz" and exfiltration-shaped argumentsu   ' to write to an OS persistence surface (SSH keys / PAM / sudoers / cron / shell rc) — this is the hermes-0day backdoor shape, not a real MCP server)r0   r=   r@   _IOC_SUBSTRINGSr<   r;   r%   _SHELL_INTERPRETERSr4   _EGRESS_PATTERNsearch_EXFIL_HINT_PATTERN_PERSISTENCE_PATTERN)	r   r5   issuesflatiocr   r   scriptissues	            r$   validate_mcp_server_entryrN   y   s    eT"" 	F uD  $;;MM5t 5 5-05 5 5  
 MMM  ii	""G ))H***EIIf--..F  f%% &4 & &7 & & & 	 %%f-- 	:99Ee ""6** 
4  7   	
 	
 	
 Mr/   boolc                <    t          t          | |                    S r*   )rO   rN   )r   r5   s     r$   is_mcp_server_entry_suspiciousrQ      s    )$66777r/   )r   r   r   r   )r&   r   r   r   )r5   r6   r   r   )r   r   r5   r6   r   rA   )r   r   r5   r6   r   rO   )__doc__
__future__r   r   rer   typingr   	frozensetrD   compile
IGNORECASErE   rG   rH   rC   r%   r4   r@   rN   rQ    r/   r$   <module>rZ      sH   2 # " " " " " 				 				       i ! ! !    "*#
 M  !bjIM   "rz: M
 
  	+ 	+ 	+ 	+      8 8 8 8v8 8 8 8 8 8r/   