Ë
    áß²j>  ã                  ó°  — U 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 ddl	m
Z
mZ ddlmZ ddlmZmZmZmZ ddlmZ d	ad
ed<   d(d„Zd)d„Zd)d„Z e
dd¬«      Zded<    G d„ de«      Zd*d„Zd+d„Zd,d„Z e h d£«      Z!dZ"d-d„Z#d.d„Z$d/d.d„Z%d0d1d„Z&d2d„Z'd3d„Z( e«       Z)ded <    ejT                  «       Z+d!Z,d/d4d"„Z-d5d#„Z.d6d$„Z/d7d%„Z0d8d&„Z1d9d'„Z2y):aÓ  Profile-scoped credential resolution for multi-profile gateway multiplexing.

The multiplexing gateway serves many profiles from one process; each profile's
``.env`` keys **cannot** be unioned into ``os.environ`` (profile A's keys would
leak into profile B's turns and subprocesses). This module is a fail-closed,
context-local secret scope: ``set_secret_scope(mapping)`` installs the active
profile's secrets for the current task (a contextvar, so it propagates into the
agent's worker thread via ``copy_context()``); ``get_secret(name)`` reads from
it and, when multiplexing is active with no scope set, RAISES rather than
falling back to ``os.environ``. Design: ``website/docs/developer-guide/multiplexing-gateway.md``.
é    )ÚannotationsN)ÚOrderedDict)Ú
ContextVarÚToken)ÚPath)ÚDictÚMappingÚOptionalÚTuple)Úfile_signatureFÚboolÚ_MULTIPLEX_ACTIVEc                ó   — t        | «      ay)zLMark whether the process is a profile multiplexer (get_secret fails closed).N)r   r   )Úactives    ú7/home/ubuntu/.hermes/hermes-agent/agent/secret_scope.pyÚset_multiplex_activer      s   € ô ˜V›Ñó    c                 ó   — t         S ©N)r   © r   r   Úis_multiplex_activer   %   s   € ÜÐr   c                 ór   — t        «       ryddlm} m}m}  | «       }|duxr  ||«       | |«       «      k7  S )ar  True when the current task runs for a profile other than the process's own: always under
    multiplexing, else when a HERMES_HOME override names another home (dashboard/desktop backend,
    per-profile cron ticker). The MCP registry scope and the check_fn cache key both follow this
    predicate so a served profile's view never aliases the launch profile's (#111151).Tr   )Úget_hermes_home_overrideÚget_process_hermes_homeÚhermes_home_keyN)r   Úhermes_constantsr   r   r   )r   r   r   Úoverrides       r   Úserves_routed_profiler   )   s>   € ô
 ÔØßcÑcÙ'Ó)€HØ˜4ÐÒk¡O°HÓ$=ÁÑQhÓQjÓAkÑ$kÐkr   Ú_SECRET_SCOPE)Údefaultz'ContextVar[Optional[Mapping[str, str]]]c                  ó&   ‡ — e Zd ZdZddˆ fd„Zˆ xZS )ÚUnscopedSecretErrora  A secret was read in multiplex mode with no scope installed.

    The fix is to wrap the call path in ``set_secret_scope(...)`` (the per-turn
    / per-adapter profile scope), not to widen the global allowlist.

    ``str(exc)`` is the ONE sentence an end user can act on; the developer diagnosis
    (which secret, which doc) rides ``__notes__`` so tracebacks and logs keep it.
    c                ó¶   •— |r|st        d„ |D «       «      rd|}}|rd|› �nd}t        ‰| �	  d|› d�«       || _        || _        |r| j                  |«       y y )Nc              3  ó<   K  — | ]  }|j                  «       –— Œ y ­wr   )Úisspace)Ú.0Úchs     r   ú	<genexpr>z/UnscopedSecretError.__init__.<locals>.<genexpr>F   s   è ø€ Ò7[È¸¿
¹
¿Ñ7[ùs   ‚Ú zthis profile's zthis profile's API keyzHermes could not read z° (an internal profile-scoping bug on the multiplexed gateway, not your configuration). Run `hermes gateway restart`; if it keeps happening, report it with `hermes debug share`.)ÚanyÚsuperÚ__init__Úsecret_nameÚdeveloper_detailÚadd_note)Úselfr-   r.   ÚwhatÚ	__class__s       €r   r,   zUnscopedSecretError.__init__B   sx   ø€ ñ Ñ/´CÑ7[È{Ô7[Ô4[Ø,.°Ð)ˆKÙ2=�  Ñ.ÐC[ˆÜ‰ÑØ$ T Fð +3ð 3ô	
ð
 'ˆÔØ 0ˆÔÙØ�M‰MÐ*Õ+ð r   )r)   r)   )r-   Ústrr.   r3   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r,   Ú__classcell__)r2   s   @r   r"   r"   8   s   ø„ ñ÷,ò ,r   r"   c                ó,   — t         j                  | «      S )zTInstall the active profile's secret mapping; ``None`` clears. Returns a reset token.)r   Úset)Úsecretss    r   Úset_secret_scoper<   T   s   € ä×Ñ˜WÓ%Ð%r   c                ó.   — t         j                  | «       y r   )r   Úreset)Útokens    r   Úreset_secret_scoper@   Y   s   € Ü×Ñ˜Õr   c                 ó*   — t         j                  «       S )z>The active secret mapping, or None when no scope is installed.)r   Úgetr   r   r   Úcurrent_secret_scoperC   ]   s   € ä×ÑÓÐr   >$   ÚTZÚPWDÚHOMEÚLANGÚPATHÚUSERÚSHELLÚLC_ALLÚTMPDIRÚ
PYTHONPATHÚHERMES_HOMEÚVIRTUAL_ENVÚSSL_CERT_FILEÚHERMES_PROFILEÚAPI_SERVER_HOSTÚAPI_SERVER_PORTÚ_HERMES_GATEWAYÚHERMES_KANBAN_DBÚGATEWAY_RELAY_URLÚAPI_SERVER_ENABLEDÚHERMES_API_TIMEOUTÚHERMES_KANBAN_BOARDÚGATEWAY_RELAY_BOT_IDSÚHERMES_MAX_ITERATIONSÚHERMES_REDACT_SECRETSÚGATEWAY_RELAY_ENDPOINTÚGATEWAY_RELAY_WAKE_URLÚAPI_SERVER_CORS_ORIGINSÚGATEWAY_RELAY_PLATFORMSÚHERMES_GATEWAY_LOCK_DIRÚGATEWAY_RELAY_ROUTE_KEYSÚGATEWAY_RELAY_INSTANCE_IDÚGATEWAY_RELAY_DISPLAY_NAMEÚHERMES_NOUS_TIMEOUT_SECONDSÚHERMES_KANBAN_WORKSPACES_ROOTÚ$GATEWAY_RELAY_ALLOW_DIRECT_PLATFORMS)ÚHERMES_KANBAN_ÚHERMES_TELEGRAM_Ú	TERMINAL_c                ó@   — | t         v xs | j                  t        «      S )z@True for genuinely process-global (non-profile-secret) env vars.)Ú_GLOBAL_ENV_EXACTÚ
startswithÚ_GLOBAL_ENV_PREFIXES)Únames    r   Ú_is_global_envrp   ‰   s   € àÔ$Ð$ÒM¨¯©Ô8LÓ(MÐMr   c                óL   — t         j                  j                  | «      }|�|S |S r   )ÚosÚenvironrB   ©ro   r    Úvals      r   Ú_environ_orrv   Ž   s#   € Ü
�*‰*�.‰.˜Ó
€CØ�/ˆ3Ð. wÐ.r   c                óò   — t        | «      rt        | |«      S t        j                  «       }|�)|j                  | «      }|�|S t        r|S t        | |«      S t        rt        | d| ›d�«      ‚t        | |«      S )u5  Resolve a credential by env-var name, honoring the active profile scope.

    Global vars always read ``os.environ``. With a scope installed, a miss returns
    ``default`` under multiplexing (never another profile's ``os.environ`` value)
    but falls through to ``os.environ`` otherwise â€” single-profile deployments
    inject credentials via the process env (systemd, ``op run``), so the scope
    must stay a ``.env`` overlay, not a blindfold (otherwise cron 401s). With no
    scope: multiplex INACTIVE reads ``os.environ``; ACTIVE raises (fail closed).
    zget_secret(aB  ) called with no profile secret scope active while multiplexing is on. This credential read must run inside a set_secret_scope(...) block (the per-turn / per-adapter profile scope). Reading os.environ here would risk leaking another profile's value. See website/docs/developer-guide/multiplexing-gateway.md (Workstream A).)rp   rv   r   rB   r   r"   )ro   r    Úscoperu   s       r   Ú
get_secretry   “   s‹   € ô �dÔÜ˜4 Ó)Ð)Ü×ÑÓ€EØÐØ�i‰i˜‹oˆØˆ?ØˆJÝ+ˆwÐK´¸TÀ7Ó1KÐKÝÜ!ØØ˜$˜ð "ð ó
ð 	
ô �t˜WÓ%Ð%r   c                ó&   — t        | |«      }|€|S |S )u·   ``get_secret`` for callers that want a ``str``: ``default`` only when the secret is genuinely
    unset. Still raises ``UnscopedSecretError`` â€” swallowing it hides a spawn-site bug.)ry   rt   s      r   Úget_secret_strr{   ²   s   € ô �T˜7Ó
#€CØ�kˆ7Ð* sÐ*r   c                ól  — | j                  «       } | s| S | d   }|dv rod}|t        | «      k  r]| |   }|dk(  r|dk(  r|dz  }Œ#||k(  r/| |dz   d j                  «       j                  d«      r| d|dz    S | S |dz  }|t        | «      k  rŒ]| S t	        j
                  d	| d¬
«      d   j                  «       S )uF  Strip a dotenv-style inline comment (python-dotenv semantics): quoted values
    scan to the matching close quote (backslash-aware for double quotes) and drop a
    trailing ``# ...``, else stay untouched; unquoted values truncate only at a
    ``#`` PRECEDED BY WHITESPACE (``foo#bar`` survives, ``value # c`` â†’ ``value``).r   )ú'ú"é   r~   ú\é   Nú#z\s+#)Úmaxsplit)ÚstripÚlenÚlstriprm   ÚreÚsplit)ÚvalueÚquoteÚir'   s       r   Ú_strip_inline_commentrŒ   ¹   sÕ   € ð
 �K‰K‹M€EÙØˆØ�!‰H€EØ�
ÑØˆØ”#�e“*ŠnØ�q‘ˆBØ˜Š|  d¢
Ø�Q‘�ØØ�UŠ{Ø).¨q°1©u¨v¨×)=Ñ)=Ó)?×)JÑ)JÈ3Ô)O�u˜W˜q 1™u�~ÐZÐUZÐZØ�‰FˆAð ”#�e“*‹nð ˆÜ�8‰8�G˜U¨QÔ/°Ñ2×8Ñ8Ó:Ð:r   c                ó¤  — | j                  «       }t        |«      dk\  r‰|d   |d   cxk(  rdk(  rxn nu|dd }g }d}|t        |«      k  rM||   dk(  xr ||dz   |dz    dv }|j                  |r||dz      n||   «       ||rdndz  }|t        |«      k  rŒMdj                  |«      S t        |«      dk\  r|d   |d   cxk(  rd	k(  r	n |S |dd S |S )
zvParse the small .env value subset Hermes writes itself (bare, 'single', or "double" with
    ``\"`` / ``\\`` escapes).r�   r   éÿÿÿÿr~   r   r€   )r~   r€   r)   r}   )r„   r…   ÚappendÚjoin)Ú	raw_valuer‰   ÚquotedÚparsedr‹   Úescapeds         r   Ú_parse_env_valuer•   Ð   sô   € ð �O‰OÓ€EÜ
ˆ5ƒz�Q‚˜5 ™8 u¨R¡yÔ7°CÕ7Ø�q˜�ˆØˆØˆØ”#�f“+ŠoØ˜Q‘i 4Ñ'ÒN¨F°1°q±5¸¸Q¹Ð,?À;Ð,NˆGØ�M‰M©7˜&  Q¡š-¸¸q¹	ÔBØ‘g‘ 1Ñ$ˆAð ”#�f“+‹oð �w‰w�v‹ÐÜ
ˆ5ƒz�Q‚˜5 ™8 u¨R¡yÔ7°CÔ7à€Lð �Q�rˆ{ÐØ€Lr   z0'OrderedDict[str, Tuple[tuple, Dict[str, str]]]'Ú_ENV_FILE_CACHEé@   c                ó¦   — t         5  | €t        j                  «        nt        j                  t	        | «      d«       ddd«       y# 1 sw Y   yxY w)z@Drop one path from the ``load_env_file()`` memo, or all of them.N)Ú_ENV_FILE_CACHE_LOCKr–   ÚclearÚpopr3   )Úenv_paths    r   Úinvalidate_env_file_cacher�   ó   s@   € ä	ñ 5ØÐÜ×!Ñ!Õ#ä×Ñ¤ H£¨tÔ4÷	5÷ 5ñ 5ús   ‡7AÁAc                óÜ   — | j                  t        j                  «      r| t        t        j                  «      d } 	 | j	                  d«      S # t
        $ r | j	                  d«      cY S w xY w)z‹BOM stripped; invalid UTF-8 falls back to latin-1 exactly as
    ``env_loader._load_dotenv_with_fallback`` installs it into ``os.environ``.Nzutf-8zlatin-1)rm   ÚcodecsÚBOM_UTF8r…   ÚdecodeÚUnicodeDecodeError)Úraws    r   Ú_decode_env_bytesr¤   ü   s\   € ð ‡~�~”f—o‘oÔ&Ø”#”f—o‘oÓ&Ð'Ð(ˆð%Ø�z‰z˜'Ó"Ð"øÜò %Ø�z‰z˜)Ó$Ò$ð%ús   ½A ÁA+Á*A+c                óZ  — i }| j                  «       D ]•  }|j                  «       }|r|j                  d«      rŒ'|j                  d«      r|t        d«      d j	                  «       }|j                  d«      \  }}}|j                  «       }|sŒ||sŒt        t        |«      «      ||<   Œ— |S )z?Tokenize already-read ``.env`` text. See :func:`load_env_file`.r‚   zexport Nú=)Ú
splitlinesr„   rm   r…   r†   Ú	partitionr•   rŒ   )Útextr;   Úraw_lineÚlineÚkeyÚsepr‰   s          r   Ú_parse_env_textr®     s    € à €GØ—O‘OÓ%ò 	JˆØ�~‰~ÓˆÙ�t—‘ sÔ+ØØ�?‰?˜9Ô%Øœ˜I›˜Ð(×/Ñ/Ó1ˆDØŸ.™.¨Ó-‰ˆˆS�%Ø�i‰i‹kˆÚ’3Ü+Ô,AÀ%Ó,HÓIˆG�CŠLð	Jð €Nr   c                óŒ  — t        | «      }	 t        | d«      5 }t        t        j                  |j                  «       «      «      }t        5  t        j                  |«      }|�=|d   |k(  r5t        j                  |«       t        |d   «      cddd«       cddd«       S ddd«       |j                  «       }t        t        j                  |j                  «       «      «      |k(  }ddd«       t        t        «      «      }r}t        5  t        |«      ft        |<   t        j                  |«       t!        t        «      t"        kD  r-t        j%                  d¬«       t!        t        «      t"        kD  rŒ-ddd«       |S |S # 1 sw Y   ŒåxY w# 1 sw Y   ŒªxY w# t        $ r t        | «       i cY S w xY w# 1 sw Y   |S xY w)u'  THE ``.env`` tokenizer: every reader (profile scope, ``hermes_cli.config.load_env``, the dashboard
    scrub, skill secret capture, managed .env, setup prompts) parses through here so no two boundaries
    disagree on which keys/values a file defines. Dict only â€” never touches ``os.environ``. ``export``
    prefix, ``#`` comments, quote escapes reversed; a BOM is stripped so it doesn't prefix the first key.
    Invalid UTF-8 decodes as latin-1, exactly like ``env_loader._load_dotenv_with_fallback`` installs it
    into ``os.environ``. Absent/unreadable â†’ ``{}``.

    Memoised per path on the open descriptor's stat identity (see the cache comment above). Always
    returns a fresh dict: callers mutate what they get back (``build_profile_secret_scope`` layers
    external secrets over it).
    ÚrbNr   r   F)Úlast)r3   Úopenr   rr   ÚfstatÚfilenor™   r–   rB   Úmove_to_endÚdictÚreadÚOSErrorr�   r®   r¤   r…   Ú_ENV_FILE_CACHE_MAXÚpopitem)rœ   r¬   ÚhandleÚfingerprintÚcachedr£   Úsettledr;   s           r   Úload_env_filer¿     sŒ  € ô ˆh‹-€CðÜ�(˜DÓ!ð 
	O VÜ(¬¯©°&·-±-³/Ó)BÓCˆKÜ%ñ +Ü(×,Ñ,¨SÓ1�ØÐ%¨&°©)°{Ò*BÜ#×/Ñ/°Ô4Ü  q¡	›?÷	+ð +÷
	Oñ 
	O÷+ð
 —+‘+“-ˆCô %¤R§X¡X¨f¯m©m«oÓ%>Ó?À;ÑNˆG÷
	Oô  Ô/°Ó4Ó5€GÙÜ!ñ 	4Ø$/´°g³Ð#?ŒO˜CÑ Ü×'Ñ'¨Ô,Ü”oÓ&Ô)<Ò<Ü×'Ñ'¨UÐ'Ô3ô ”oÓ&Ô)<Ó<÷	4ð
 €Nˆ7€N÷++ð +ú÷
	Oð 
	Oûô ò ä! (Ô+ØŠ	ðú÷	4ð
 €Nús[   �F ™3FÁAFÂ	FÂ	F Â!AFÃ(F ÄA,F9ÆF	Æ
FÆFÆF ÆF6Æ5F6Æ9Gc                ó*  — t        t        | «      dz  «      }	 ddlm}  |t        | «      «      }|j                  d„ |j                  «       D «       «       ddlm}  |«       }|�t        | «      r|j                  d|«       |S # t        $ r i }Y Œaw xY w)uÝ   Build a profile's secret mapping from ``<home>/.env`` plus its external
    secret sources. Global vars are NOT copied in â€” ``get_secret`` reads those
    from ``os.environ`` â€” so the scope holds only profile secrets.z.envr   )Úget_secret_source_valuesc              3  óB   K  — | ]  \  }}t        |«      rŒ||f–— Œ y ­wr   )rp   )r&   ÚkÚvs      r   r(   z-build_profile_secret_scope.<locals>.<genexpr>I  s    è ø€ ÒX™d˜a ÄnÐUVÕFW�A�q”6ÑXùs   ‚–	)Úbridged_allow_all_usersÚGATEWAY_ALLOW_ALL_USERS)r¿   r   Úhermes_cli.env_loaderrÁ   Ú	ExceptionÚupdateÚitemsÚgateway.config_loaderrÅ   Ú_is_process_homeÚ
setdefault)Úhermes_homer;   rÁ   Úexternal_secretsrÅ   Úbridgeds         r   Úbuild_profile_secret_scoperÑ   ?  s–   € ô œD Ó-°Ñ6Ó7€GðÝBÙ3´D¸Ó4EÓFÐð ‡N�NÑXÐ&6×&<Ñ&<Ó&>ÔXÔXõ >Ù%Ó'€GØÐÔ/°Ô<Ø×ÑÐ4°gÔ>Ø€Nøô ò ØÒðús   ™B ÂBÂBc                óŒ   — ddl m} 	 t        | «      j                  «        |«       j                  «       k(  S # t        $ r Y yw xY w)Nr   )r   F)r   r   r   Úresolver¸   )rÎ   r   s     r   rÌ   rÌ   T  sB   € Ý8ðÜ�KÓ ×(Ñ(Ó*Ñ.EÓ.G×.OÑ.OÓ.QÑQÐQøÜò Ùðús   ˆ.7 ·	AÁA)r   r   ÚreturnÚNone)rÔ   r   )r;   úOptional[Mapping[str, str]]rÔ   r   )r?   r   rÔ   rÕ   )rÔ   rÖ   )ro   r3   rÔ   r   )ro   r3   r    úOptional[str]rÔ   r×   r   )r)   )ro   r3   r    r3   rÔ   r3   )r‰   r3   rÔ   r3   )r‘   r3   rÔ   r3   )rœ   zOptional[Path]rÔ   rÕ   )r£   ÚbytesrÔ   r3   )r©   r3   rÔ   úDict[str, str])rœ   r   rÔ   rÙ   )rÎ   r   rÔ   rÙ   )rÎ   r   rÔ   r   )3r7   Ú
__future__r   rŸ   rr   r‡   Ú	threadingÚcollectionsr   Úcontextvarsr   r   Úpathlibr   Útypingr   r	   r
   r   Úutilsr   r   Ú__annotations__r   r   r   r   ÚRuntimeErrorr"   r<   r@   rC   Ú	frozensetrl   rn   rp   rv   ry   r{   rŒ   r•   r–   ÚLockr™   r¹   r�   r¤   r®   r¿   rÑ   rÌ   r   r   r   ú<module>rå      sù   ðò
õ #ã Û 	Û 	Û Ý #ß )Ý ß 1Ó 1å  ð
  Ð �4Ó ó%óó	lñ :DÀOÐ]aÔ9b€Ð6Ó bô,˜,ô ,ó8&ó
óñ ò ó Ð ð8Ð óNó
/ô
&ô>+ó;ó.ñ< EPÃM€ÐAÓ QØ%�y—~‘~Ó'Ð ØÐ ô5ó%óó %óPô*r   