+
    0å·jÌ  ã                   óÚ   € R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHt Rt	Rt
^t^t^<tRt^t0 Rmt]P"                  ! R4      tR t]! RR	7       ! R
 R4      4       tR t ! R R4      tR# )uL  Remote access to the MCP bridge: through a domain name (e.g. frp â†’ this machine), with a
password, instead of only from a browser on this machine.

    "remote": {"hosts": ["mcp.example.com"], "session_hours": 12}

and the password in the environment variable WEBTT_MCP_PASSWORD (16+ characters). A request
whose Host is one of `hosts` must carry a session cookie from POST /api/mcp/login. The
cookie is HttpOnly, SameSite=Strict and â€” unless "insecure_http": true â€” Secure, so the
domain must be served over HTTPS. Sessions live in memory: restarting serve.py ends them.

Behind a proxy every request arrives from 127.0.0.1, and a proxy that rewrites Host to
localhost (e.g. frp's hostHeaderRewrite) would make internet requests look local. So once
"remote" is configured, localhost is not trusted either: it logs in like a remote host and
reaches the same servers. "trust_localhost": true restores the no-login local flow â€” only for
setups where nothing in front of serve.py can rewrite Host.

Every request arrives from 127.0.0.1, so failed logins can't be limited per
client: after FREE_ATTEMPTS failures in a row, login is locked for 60 s, doubling with each
further failure up to MAX_LOCK. While locked even the right password is refused, so the lock
gives nothing away. Local access (a browser on this machine) is unaffected by all of this.
N)Ú	dataclassÚmcp_sessionÚWEBTT_MCP_PASSWORDé  z^[A-Za-z0-9.-]+(:[0-9]{1,5})?$c                ó  € V P                  R4      '       g(   V P                  4       P                  R^4      ^ ,          M,V P                  4       P                  R4      ^ ,          R,           pVR9   ;'       g    VP                  R4      # )z2localhost / 127.x / [::1], with or without a port.Ú[Ú:Ú]z127.)Ú	localhostz[::1])Ú
startswithÚlowerÚrsplitÚsplit)ÚhostÚnames   & Ú*/work/data/stt/webtt/scripts/mcp_remote.pyÚis_local_namer   )   sj   € à15·±À×1EÒ1Eˆ4�:‰:‹<×Ñ˜s AÓ& qÖ)È4Ï:É:Ë<×K]ÑK]Ð^aÓKbÐcdÕKeÐhkÕKk€DØÐ)Ñ)×DÐD¨T¯_©_¸VÓ-DÐDó    T)Úfrozenc                   ó4   a € ] tR t^/t o RtRtV 3R ltRtV tR# )ÚLoginResultNc                óN   <€ V ^8„  d   Qh/ S[ R,          ;R&   S[R,          ;R&   # )é   NÚtokenÚretry_after)ÚstrÚint)ÚformatÚ__classdict__s   "€r   Ú__annotate__ÚLoginResult.__annotate__/   s%   ø‡ ‚ á��:Ññ ñ �t•Ñ"ò r   © )	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__r   r   Ú__annotate_func__Ú__static_attributes__Ú__classdictcell__©r   s   @r   r   r   /   s   ø‡ € à€EØ"€K÷ ƒ r   r   c                 óf   € \         P                  ! V P                  4       4      P                  4       # ©N)ÚhashlibÚsha256ÚencodeÚdigest)Útexts   &r   Ú_digestr1   5   s    € Ü�>Š>˜$Ÿ+™+›-Ó(×/Ñ/Ó1Ð1r   c                   ó¨   a € ] tR t^9t o R]P
                  3R lt]]P
                  3R l4       tR t	R t
R tR tR tR	 tR
 tR tR tR tRtV tR# )ÚRemoteAccessFc                ó,  € V Uu0 uF  qwP                  4       kK  	  upV n        WPn        \        V4      V n        \        VR ,          4      V n        W@n        W`n        \        P                  ! 4       V n        / V n        V P                  4        R# u upi )r   N)r   ÚhostsÚtrust_localhostr1   Úpasswordr   Úsession_secondsÚinsecure_httpÚclockÚ	threadingÚLockÚlockÚsessionsÚreset_limits)Úselfr5   r7   Úsession_hoursr9   r6   r:   Úhs   &&&&&&& r   Ú__init__ÚRemoteAccess.__init__:   sq   € Ù).Ó/© A—g‘g–i©Ñ/ˆŒ
Ø.ÔÜ Ó)ˆŒÜ" =°4Õ#7Ó8ˆÔØ*ÔØŒ
Ü—N’NÓ$ˆŒ	ØˆŒØ×ÑÖùò 0s   …Bc                ó„  € Vf   R# \        V\        4      '       d   \        V4      \        ,
          '       d+   \	        RRP                  \        \        4      4       24      hVP                  R4      p\        V\        4      '       dC   V'       d;   \        ;QJ d    R V 4       F  '       d   K   RM	  RM! R V 4       4      '       g   \	        R4      h\        ;QJ d    R	 V 4       F  '       g   K   RM	  RM! R	 V 4       4      '       d   \	        R
4      hVP                  R\        4      p\        V\        \        34      '       d(   \        V\        4      '       g   ^ Tu;8  d   R8:  g   M \	        R4      hVP                  RR4      pVP                  RR4      p\        V\        4      '       d   \        V\        4      '       g   \	        R4      hVP                  \        R4      pV'       g   \	        R\         24      h\!        V4      \"        8  d   \	        \         R\"         R24      hV ! WHWVWs4      # )zANone without a "remote" section; raises ValueError for a bad one.Nz"remote" takes only z, r5   c              3   ó|   "  € T F2  p\        V\        4      ;'       d    \        P                  V4      x € K4  	  R # 5ir+   )Ú
isinstancer   ÚHOST_REÚmatch©Ú.0rB   s   & r   Ú	<genexpr>Ú+RemoteAccess.from_config.<locals>.<genexpr>M   s2   é € Ð9qÑkpÐfg¼*ÀQÌÓ:L×:aÐ:aÔQX×Q^ÑQ^Ð_`ÓQaÔ:aÓkpùs   ‚<ž<FTzM"remote.hosts" must be a non-empty list of host names (optionally with :port)c              3   ó8   "  € T F  p\        V4      x € K  	  R # 5ir+   )r   rJ   s   & r   rL   rM   O   s   é € Ð/© AŒ}˜Q×Ð«ùs   ‚zL"remote.hosts" lists a local name: localhost is handled by "trust_localhost"rA   zB"remote.session_hours" must be a number of hours between 0 and 720r9   r6   zI"remote.insecure_http" and "remote.trust_localhost" must be true or falseÚ zE"remote" is configured: set the password in the environment variable z must be at least z charactersiÐ  )rG   ÚdictÚsetÚKEYSÚ
ValueErrorÚjoinÚsortedÚgetÚlistÚallÚanyÚDEFAULT_SESSION_HOURSr   ÚfloatÚboolÚPASSWORD_ENVÚlenÚMIN_PASSWORD)	ÚclsÚcfgÚenvr:   r5   ÚhoursÚinsecureÚtrustr7   s	   &&&&     r   Úfrom_configÚRemoteAccess.from_configE   s©  € ð Š;ÙÜ˜#œt×$Ò$¬¨C«´4¯¬ÜÐ3°D·I±I¼fÄT»lÓ4KÐ3LÐMÓNÐNØ—‘˜Ó ˆÜ˜5¤$×'Ò'¯E·c³cÑ9qÑkpÓ9q·c·c²cÑ9qÑkpÓ9q×6qÒ6qÜÐlÓmÐmß‹3Ñ/©Ó/�3�3Š3Ñ/©Ó/×/Ò/ÜÐkÓlÐlØ—‘˜Ô)>Ó?ˆÜ˜5¤3¬ ,×/Ò/¼
À5Ì$×8OÒ8OÐTUÐX]ÖThÐahÖThÜÐaÓbÐbØ—7‘7˜?¨EÓ2ˆØ—‘Ð)¨5Ó1ˆÜ˜8¤T×*Ò*¬z¸%Ä×/FÒ/FÜÐhÓiÐiØ—7‘7œ<¨Ó,ˆßÜÐdÔeqÐdrÐsÓtÐtÜˆx‹=œ<Ô'Ü¤˜~Ð-?Ä¸~È[ÐYÓZÐZÙ�5 E°UÓBÐBr   c                ó<   € VP                  4       V P                  9   # r+   )r   r5   )r@   r   s   &&r   Úis_remote_hostÚRemoteAccess.is_remote_hosta   s   € Ø�z‰z‹|˜tŸz™zÑ)Ð)r   c                óª   € VP                  4       pV P                  '       g   \        V4      '       d   RMRpY# Uu0 uF	  qD RV 2kK  	  up9   # u upi )Úhttpsz://)rl   Úhttp)rl   )r   r9   r   )r@   r   ÚoriginÚschemesÚss   &&&  r   Ú	origin_okÚRemoteAccess.origin_okd   sP   € à�z‰z‹|ˆØ'+×'9×'9Ð'9¼]È4×=PÒ=PÑ#ÐV`ˆØ°7Ó;±7¨a˜C˜s 4 &›/±7Ñ;Ñ;Ð;ùÒ;s   ¼Ac                óª   € V P                   ;_uu_ 4        ^ R\        ^,          uV n        V n        V n        RRR4       R#   + '       g   i     R# ; i)é    g        N)r=   Ú
FIRST_LOCKÚfailuresÚlocked_untilÚlock_seconds)r@   s   &r   r?   ÚRemoteAccess.reset_limitsl   s4   € Ø�Y�Y‹YØBCÀSÌ*ÐXYÍ/Ð?ˆDŒM˜4Ô,¨dÔ.?÷ �Y�YŠYús   •"AÁA	c                ó¬  € V P                  4       pV P                  ;_uu_ 4        W P                  8  d8   \        \	        V P                  V,
          R ,           4      R7      uuRRR4       # \
        P                  ! \        V4      V P                  4      '       d¡   ^ \        ^,          uV n
        V n        \        P                  ! ^ 4      pV P                  P                  4        UUu/ uF  w  rEWR8”  g   K  WEbK  	  uppV n        W P                   ,           V P                  V&   \        VR7      uuRRR4       # V ;P                  ^,          un
        V P                  \"        8¼  d>   \%        V P                  ^,          \&        4      V n        W P                  ,           V n        \        4       uuRRR4       # u uppi   + '       g   i     R# ; i)g+‡ÙÎ÷ï?)r   N)r   )r:   r=   rw   r   r   ÚhmacÚcompare_digestr1   r7   ru   rv   rx   ÚsecretsÚtoken_urlsafer>   Úitemsr8   ÚFREE_ATTEMPTSÚminÚMAX_LOCK)r@   r7   Únowr   ÚtÚes   &&    r   ÚloginÚRemoteAccess.loginp   sA  € Ø�j‰j‹lˆØ�Y�Y‹YØ×&Ñ&Ô&Ü"¬s°4×3DÑ3DÀsÕ3JÈUÕ3RÓ/SÔT÷ ŠYô ×"Ò"¤7¨8Ó#4°d·m±m×DÒDØ34´jÀAµoÐ0�”˜tÔ0Ü×-Ò-¨bÓ1�Ø26·-±-×2EÑ2EÔ2GÔ SÑ2G©$¨!È1É7¤ ¢Ñ2GÒ S�”Ø'*×-AÑ-AÕ'A�—‘˜eÑ$Ü"¨Ô/÷ ŠYð �MŠM˜QÕ�MØ�}‰}¤Ô-Ü$'¨×(9Ñ(9¸AÕ(=¼xÓ$H�Ô!Ø$'×*;Ñ*;Õ$;�Ô!Ü“=÷ ŠYùó !T÷ �YˆYús1   ¥=GÁ-A=GÃ*
F<
Ã9F<
Ã?3GÄ=A4GÆ<GÇG	c                ó*  € V'       g   R # V P                   ;_uu_ 4        V P                  P                  V4      pVf    RRR4       R # W P                  4       8:  d   V P                  V  RRR4       R #  RRR4       R#   + '       g   i     R# ; i)FNT)r=   r>   rV   r:   )r@   r   Úexpirys   && r   ÚvalidÚRemoteAccess.valid�   si   € ßÙØ�Y�Y‹YØ—]‘]×&Ñ& uÓ-ˆFØŠ~Ø÷ ‰Yð Ÿ™›Ô%Ø—M‘M %Ð(Ø÷ ‰Yð ÷ �Y�YŠYús   Ÿ!BÁ
"BÁ6BÂB	c                óð   € V P                   ;_uu_ 4        \        ^ \        V P                  P	                  V^ 4      V P                  4       ,
          4      4      uuRRR4       #   + '       g   i     R# ; i)rt   N)r=   Úmaxr   r>   rV   r:   ©r@   r   s   &&r   Ú
expires_inÚRemoteAccess.expires_in�   sB   € Ø�Y�Y‹YÜ�qœ#˜dŸm™m×/Ñ/°°qÓ9¸D¿J¹J»LÕHÓIÓJ÷ �Y�Y‹Yús   •AA$Á$A5	c                ó    € V P                   ;_uu_ 4        V P                  P                  VR 4       R R R 4       R #   + '       g   i     R # ; ir+   )r=   r>   ÚpoprŽ   s   &&r   ÚlogoutÚRemoteAccess.logout‘   s+   € Ø�Y�Y‹YØ�M‰M×Ñ˜e TÔ*÷ �Y�YŠYús	   •<¼A	c                ób   € V P                   '       d   R MRp\         RV RV P                   V 2# )rO   ú; SecureÚ=z0; Path=/api; HttpOnly; SameSite=Strict; Max-Age=)r9   ÚSESSION_COOKIEr8   )r@   r   Úsecures   && r   Úcookie_headerÚRemoteAccess.cookie_header•   s;   € Ø×)×)Ð)‘¨zˆÜ Ð!  5 'Ð)YÐZ^×ZnÑZnÐYoÐpvÐowÐxÐxr   c                óD   € V P                   '       d   R MRp\         RV 2# )rO   r–   z2=; Path=/api; HttpOnly; SameSite=Strict; Max-Age=0)r9   r˜   )r@   r™   s   & r   Úclear_cookie_headerÚ RemoteAccess.clear_cookie_header™   s)   € Ø×)×)Ð)‘¨zˆÜ Ð!Ð!SÐTZÐS[Ð\Ð\r   c                ó  € R RP                  \        V P                  4      4       RV P                  R,           R2.pTP	                  RV P
                  '       d   RMR,           4       V P                  '       d   VP	                  R4       V# )	u     è¿œç¨‹è®¿é—®ï¼šu   ã€�u   ï¼ˆéœ€è¦�å¯†ç �ï¼Œä¼šè¯� r   u
    å°�æ—¶ï¼‰u     æœ¬æœº localhostï¼šu[   ä¸�ç”¨ç™»å½•ï¼ˆtrust_localhostï¼šç¡®ä¿�å‰�é�¢çš„ä»£ç�†ä¸�ä¼šæŠŠ Host æ”¹å†™æˆ� localhostï¼‰ud   å�Œæ ·éœ€è¦�ç™»å½•ï¼Œå�ªèƒ½ç”¨è¿œç¨‹å�¯ç”¨çš„æœ�åŠ¡ç«¯ï¼ˆè®¾ "trust_localhost": true å�¯å…�ç™»å½•ï¼‰ua     âš  insecure_httpï¼šå¯†ç �å’Œä¼šè¯�ä»¤ç‰Œç»� http æ˜Žæ–‡ä¼ è¾“ï¼Œå�ªåœ¨å�¯ä¿¡ç½‘ç»œé‡Œè¿™æ ·ç”¨)rT   rU   r5   r8   Úappendr6   r9   )r@   Úliness   & r   Ústartup_linesÚRemoteAccess.startup_lines�   s¢   € Ø$ U§Z¡Z´°t·z±zÓ0BÓ%CÐ$DÐD]Ð^b×^rÑ^rÐvzÕ^zÐ]{ð  |Fð  Gð  HˆØ�‰Ð,ð  RV÷  Rf÷  Rfð  Rfñ  1Nð 2XõYô 	Zà××ÐØ�L‰LÐ|Ô}Øˆr   )r:   rv   r5   r9   r=   rx   rw   r7   r8   r>   r6   N)r"   r#   r$   r%   ÚtimeÚ	monotonicrC   Úclassmethodrf   ri   rq   r?   r†   rŠ   r�   r“   rš   r�   r¢   r'   r(   r)   s   @r   r3   r3   9   sn   ø‡ € ØV[Ðcg×cqÑcqô 	ð Ø)-¯©ó Có ðCò6*ò<òZò!ò"
òKò+òyò]÷ð r   r3   >   r5   r9   rA   r6   )Ú__doc__r,   r{   Úrer}   r;   r¤   Údataclassesr   r˜   r]   r_   r€   ru   r‚   rZ   rR   ÚcompilerH   r   r   r1   r3   r!   r   r   Ú<module>r«      sŽ   ðñó* Û Û 	Û Û Û Ý !à€Ø#€Ø€Ø€Ø€
Ø€ØÐ ÚE€Ø
�*Š*Ð6Ó
7€òEñ �$Ô÷#ð #ó ð#ò
2÷jó jr   