
    xj*                        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	m
Z
  ej        e          ZdZdZdefdeffZh d	Zh d
Zd'd(dZd)dZd*dZd+dZedfd,dZd-d.d"Zd-d.d#Zd/d$Zd0d%Zd-d.d&ZdS )1a{  Safe server-side probe for the official Hermes Agent dashboard.

The official `hermes dashboard` binds to 127.0.0.1:9119 by default and exposes
GET /api/status as a public, read-only identity/status endpoint.  Keep all
probing server-side to avoid browser CORS/mixed-content failures, and only allow
loopback targets so a user-controlled setting cannot become an SSRF primitive.
    )annotationsN)urlparse
urlunparsei#  g      ?	127.0.0.1	localhost>   autoneveralways>   ::1r   r   httphoststrportintschemereturnc                X    d| v r|                      d          sd|  dn| }| d| d| S )N:[]z://)
startswith)r   r   r   display_hosts       6/Users/bertmahoney/hermes-webui/api/dashboard_probe.py	_base_urlr      sH    "%++dooc6J6J+;t;;;;PTL.......    raw_url
str | None tuple[str, int, str, str] | Nonec                   t          | pd                                          }|sdS t          |          }|j        dvrt	          d          |j        s|j        rt	          d          |j        pd}|                                                                }|t          vrt	          d          	 |j
        }n"# t          $ r}t	          d          |d}~ww xY wt          |t                    rd|cxk    rd	k    sn t	          d          |j        pd}|d
vs|j        s|j        s|j        rt	          d          t#          |||j                  }|||j        |fS )a]  Return (host, port, scheme, base_url) for a safe loopback dashboard URL.

    Overrides intentionally accept only scheme + loopback host + explicit port.
    Paths, query strings, fragments, and credentials are rejected: the probe
    appends the official `/api/status` fingerprint itself and must not become an
    arbitrary local URL fetcher.
     N>   r   httpsinvalid dashboard URL scheme!invalid dashboard URL credentialsinvalid dashboard URL hostinvalid dashboard URL port     r    /invalid dashboard URL path)r   stripr   r   
ValueErrorusernamepasswordhostnamelower_LOOPBACK_HOSTSr   
isinstancer   pathparamsqueryfragmentr   )	r   rawparsedr   normalized_hostr   excr3   bases	            r   normalize_dashboard_urlr<      s    gm


"
"
$
$C tc]]F}---7888 >&/ ><===? bDjjll((**Oo--5666@{ @ @ @566C?@dC   7d););););e););););5666;"D95666_dFM::DD&-55s   4B< <
CCCc                   t          | pd                                          }|sdS t          |          }|j        dvrt	          d          |j        s|j        rt	          d          |j        st	          d          |j        s|j	        s|j
        rt	          d          |j        pd}|dvrt	          d          	 |j        }n"# t          $ r}t	          d          |d	}~ww xY w|j                                        }d
|v r|                    d          sd| d}|}|%d|cxk    rdk    sn t	          d          | d
| }t          |j        |ddddf          S )ab  Return a safe browser-only dashboard link URL.

    Unlike the server-side probe target, this value is only returned to the
    browser for navigation.  It may point at a public reverse-proxy hostname, but
    it still rejects credentials, paths, query strings, fragments, and non-HTTP
    schemes so it cannot hide secrets or script URLs in config.
    r    >   r   r!   r"   r#   r$   r*   r(   r%   Nr   r   r   r&   r'   )r   r+   r   r   r,   r-   r.   r/   r4   r5   r6   r3   r   r0   r   r   )r   r7   r8   r3   r   r:   r   netlocs           r   normalize_dashboard_browser_urlr?   @   s    gm


"
"
$
$C rc]]F}---7888 >&/ ><===? 75666} 7 7 75666;"D95666@{ @ @ @566C?@?  ""D
d{{4??3//{4{{{FT""""U""""9:::##T##v}fb"b"=>>>s   C 
C*C%%C*payloadobjectboolc                     t           t                    sdS                      d          }t          |t                    r|                                sdS t           fddD                       S )NFversionc              3      K   | ]}|v V  	d S N ).0keyr@   s     r   	<genexpr>z1_looks_like_official_dashboard.<locals>.<genexpr>p   s'      kk#sg~kkkkkkr   )release_datehermes_homeconfig_pathgateway_running)r2   dictgetr   r+   any)r@   rD   s   ` r   _looks_like_official_dashboardrR   f   sw    gt$$ ukk)$$Ggs## 7==?? u
 kkkk)jkkkkkkr   timeoutfloatrO   c                   	 t          | pd                                                                          }|t          vrt	          d          t          |          }d|cxk    rdk    sn t	          d          |dvrt	          d          t          |||          }t          j        	                    | dd	d
d          }t          j        
                    ||          5 }t          |dd          dk    rddicddd           S t          j        |                                                    d                    }ddd           n# 1 swxY w Y   t!          |          sddiS d|||d}	|                    d          }
t%          |
t                     r+|
                                r|
                                |	d<   |	S # t&          $ r# t(                              dd           ddicY S w xY w)zBBest-effort check that `hermes dashboard` is running on host:port.r    z%dashboard probe host must be loopbackr&   r'   z!dashboard probe port out of range>   r   r!   z,dashboard probe scheme must be http or httpsz/api/statuszapplication/jsonzhermes-webui-dashboard-probe)Acceptz
User-Agent)headers)rS   statusN   runningFzutf-8T)rZ   r   r   urlrD   z&official Hermes dashboard probe failed)exc_info)r   r+   r0   r1   r,   r   r   urllibrequestRequesturlopengetattrjsonloadsreaddecoderR   rP   r2   	Exceptionloggerdebug)r   r   rS   r   r9   r;   r^   responser@   resultrD   s              r   probe_official_dashboardrk   s   s   "djb////117799/11DEEE4yyT""""U""""@AAA***KLLL$77.((   1A_`` ) 
 
 ^##GW#== 	Bx400C77!5)	B 	B 	B 	B 	B 	B 	B 	B j!7!7!@!@AAG	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B .g66 	&u%%!?DQUVV++i((gs## 	0 	0 'F9 " " "=MMM5!!!!"sI   C&G (EG 9EG EG EG 1AG *G<;G<config_datadict | Nonec                4   | $	 ddl m}  |            } n# t          $ r i } Y nw xY wt          | t                    r|                     di           ni }t          |t                    r|                    di           ni }t          |t                    r|ni S )Nr   )
get_configwebui	dashboard)
api.configro   rf   r2   rO   rP   )rl   ro   	webui_cfgdashboard_cfgs       r   _dashboard_configru      s    	------$*,,KK 	 	 	KKK	0:;0M0MU,,,SUI6@D6Q6QYIMM+r222WYM&}d;;C==Cs    $$c                `   t          |           }t          |                    dd          pd                                                                          }|t
          vrd}t          |                    d          pd                                          }|rt          |          }||dS )uF   Return normalized profile config for the Settings → System controls.enabledr   r[   r    rw   r[   )ru   r   rP   r+   r0   _DASHBOARD_ENABLED_VALUESr?   )rl   rt   rw   r   s       r   get_dashboard_configrz      s    %k22M-##Iv66@&AAGGIIOOQQG///-##E**0b117799G ;1'::w///r   c                &   t          | pi                     dd          pd                                                                          }|t          vrt          d          t          | pi                     dd          pd                                          }|rt          |          nd}ddlm} |	                                }|
                    |          }|                    d          }t          |t                    si }||d<   |                    d	          }t          |t                    si }||d	<   ||d<   |r||d<   n|                    dd
           |                    ||           |                                 ||dS )zEPersist dashboard link settings under webui.dashboard in config.yaml.rw   r   zinvalid dashboard enabled moder[   r    r   )configrp   rq   Nrx   )r   rP   r+   r0   ry   r,   r?   apir|   _get_config_path_load_yaml_config_filer2   rO   pop_save_yaml_config_filereload_config)	r@   rw   r   normalized_urlwebui_configrM   rl   webui_sectiondashboard_sections	            r   save_dashboard_configr      s   7=b%%i88BFCCIIKKQQSSG///9:::7=b%%eR006B77==??GAHP4W===bN******//11K55kBBKOOG,,MmT** -,G%))+66'.. 7%6k"#*i  +#1%  eT***''[AAA   ~666r   c                    t          t          j                            d          pd                                                                          } |                     dd                              dd          }|t          v S )NHERMES_WEBUI_HOSTr   r   r    r   )r   osenvironrP   r+   r0   replacer1   )raw_hostr   s     r   "_webui_bind_host_allows_auto_prober      sk    2:>>"566E+FFLLNNTTVVHC$$,,S"55D?""r   c                   t          |           }t          |                    dd          pd                                                                          }|t
          vrd}|dk    rdddS |                    d          p|                    d          pd}	 |rt          |          nd}n# t          $ r	 d|d	d
cY S w xY w	 t          |          }n# t          $ r d}Y nw xY w|r|g}nd t          D             }|dk    r%|r	|sd|||dS |d         \  }}}	}
d||||p|
|p|
dS t                      sd|dS |D ]I\  }}}	}t          ||t          |	          }|                    d          r||d<   |r
||d<   ||d<   |c S Jd|dS )zEReturn the safe status payload consumed by GET /api/dashboard/status.rw   r   r	   F)rZ   rw   r[   targetr    zinvalid dashboard url)rZ   rw   errorNc           	     <    g | ]\  }}||d t          ||          fS r   )r   )rH   r   r   s      r   
<listcomp>z(get_dashboard_status.<locals>.<listcomp>   s/    kkk:4D$	$(=(=>kkkr   r
   T)rZ   rw   r[   browser_urlr   )rZ   rw   r   r   r[   r   )rS   r   rZ   r   )ru   r   rP   r+   r0   ry   r?   r,   r<   DEFAULT_DASHBOARD_TARGETSr   rk   DEFAULT_DASHBOARD_TIMEOUT)rl   rt   rw   r   r   overridetargetsr   r   r   r;   _baserj   s                r   get_dashboard_statusr      sq   %k22M-##Iv66@&AAGGIIOOQQG///' W555&&K-*;*;H*E*EKGXBIQ5g>>>r X X X W?VWWWWWX*733     l*kkQjkkk( 	ix 	i#\ghhh#*1: dFDGT4XcXkgk  }H  }P  LP  Q  Q  	Q-// 6 W555%,  !dFE)$>W`fggg::i   	 'F9 ,(3}% +uMMM	 111s$   B0 0CCC C&%C&r   )r   r   r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   )r@   rA   r   rB   )
r   r   r   r   rS   rT   r   r   r   rO   rF   )rl   rm   r   rO   )r@   rO   r   rO   )r   rB   )__doc__
__future__r   rb   loggingr   urllib.requestr]   urllib.parser   r   	getLogger__name__rg   DEFAULT_DASHBOARD_PORTr   r   ry   r1   r   r<   r?   rR   rk   ru   rz   r   r   r   rG   r   r   <module>r      s    # " " " " "   				     - - - - - - - -		8	$	$  )+AB[RhDij 777 333/ / / / /
6 6 6 6B#? #? #? #?L
l 
l 
l 
l  /	"" "" "" "" ""J
D 
D 
D 
D 
D	0 	0 	0 	0 	07 7 7 7<# # # #*2 *2 *2 *2 *2 *2 *2r   