
    `gj!                        U d Z ddlmZ ddlZddlmZmZ ddlmZ ddl	m
Z
mZmZ daded	<   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!dZd"dZd#dZy)$u  Profile-scoped credential resolution for multi-profile gateway multiplexing.

The multiplexing gateway serves many profiles from one process. Each profile
has its own ``.env`` with its own provider keys and platform tokens, so we
**cannot** union them into the process-global ``os.environ`` (that would leak
profile A's keys to profile B's turns, and to every subprocess spawned with
``env=dict(os.environ)``).

This module provides 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()`` exactly like the HERMES_HOME override).
- ``get_secret(name)`` reads from that scope. When multiplexing is **active**
  and no scope is set, it RAISES rather than silently falling back to
  ``os.environ`` — an un-migrated or newly-added call site fails loud at that
  exact line instead of leaking another profile's value. When multiplexing is
  **off** (the default), it transparently reads ``os.environ`` so the
  single-profile gateway and every non-gateway caller behave exactly as before.

Design rationale lives in ``docs/design/multiplexing-gateway.md`` (Workstream A).
    )annotationsN)
ContextVarToken)Path)DictMappingOptionalFbool_MULTIPLEX_ACTIVEc                    t        |       ay)zMark whether the process is running as a profile multiplexer.

    Called once at gateway startup. When True, ``get_secret`` fails closed on
    an unscoped read instead of falling back to ``os.environ``.
    N)r
   r   )actives    E/root/.hermes/venv/lib/python3.12/site-packages/agent/secret_scope.pyset_multiplex_activer   '   s     V    c                     t         S )z?Return whether the process is running as a profile multiplexer.)r    r   r   is_multiplex_activer   1   s    r   _SECRET_SCOPE)defaultz'ContextVar[Optional[Mapping[str, str]]]c                      e Zd ZdZy)UnscopedSecretErrora  Raised when a secret is read in multiplex mode with no scope installed.

    This is the fail-closed signal: it means a credential read reached
    ``get_secret`` without a profile scope active, which in a multiplexer would
    otherwise leak whichever profile's value happened to be in ``os.environ``.
    The fix is to wrap the call path in ``set_secret_scope(...)`` (the per-turn
    / per-adapter profile scope), not to widen the allowlist.
    N)__name__
__module____qualname____doc__r   r   r   r   r   <   s    r   r   c                ,    t         j                  |       S )zInstall the active profile's secret mapping for the current context.

    Returns a token for ``reset_secret_scope``. Pass ``None`` to clear.
    )r   set)secretss    r   set_secret_scoper   G   s    
 W%%r   c                .    t         j                  |        y)z"Restore the previous secret scope.N)r   reset)tokens    r   reset_secret_scoper#   O   s    r   c                 *    t         j                         S )zEReturn the active secret mapping, or None when no scope is installed.)r   getr   r   r   current_secret_scoper&   T   s    r   >   TZPWDHOMELANGPATHUSERSHELLLC_ALLTMPDIR
PYTHONPATHHERMES_HOMEVIRTUAL_ENVSSL_CERT_FILEHERMES_PROFILE_HERMES_GATEWAYHERMES_KANBAN_DBHERMES_MAX_TOKENSHERMES_API_TIMEOUTHERMES_KANBAN_BOARDHERMES_MAX_ITERATIONSHERMES_REDACT_SECRETSHERMES_GATEWAY_LOCK_DIRHERMES_NOUS_TIMEOUT_SECONDSHERMES_KANBAN_WORKSPACES_ROOT)HERMES_KANBAN_HERMES_TELEGRAM_	TERMINAL_c                F      t         v ryt         fdt        D              S )zGReturn True for genuinely process-global (non-profile-secret) env vars.Tc              3  @   K   | ]  }j                  |        y wN)
startswith).0pnames     r   	<genexpr>z!_is_global_env.<locals>.<genexpr>x   s     @atq!@s   )_GLOBAL_ENV_EXACTany_GLOBAL_ENV_PREFIXES)rH   s   `r   _is_global_envrM   t   s!      @+?@@@r   c                0   t        |       r%t        j                  j                  |       }||S |S t        j                         }||j                  |       }||S |S t
        rt        d| d      t        j                  j                  |       }||S |S )uZ  Resolve a credential by env-var name, honoring the active profile scope.

    Resolution order:

    1. Genuinely-global vars (``_is_global_env``) always read ``os.environ`` —
       they are deployment settings, not profile secrets.
    2. When a secret scope is installed (multiplexed turn), read from it; an
       absent key returns ``default``. The scope is authoritative — we do NOT
       fall through to ``os.environ``, because in a multiplexer ``os.environ``
       may hold another profile's value.
    3. No scope installed:
       - multiplex INACTIVE (default deployment): read ``os.environ`` —
         identical to the legacy ``os.getenv`` behavior every caller had before.
       - multiplex ACTIVE: FAIL CLOSED. Raise ``UnscopedSecretError`` so the
         missing scope is caught loudly instead of leaking a cross-profile value.
    zget_secret(a1  ) 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 docs/design/multiplexing-gateway.md (Workstream A).)rM   osenvironr%   r   r   r   )rH   r   valscopes       r   
get_secretrS   {   s    " djjnnT"os272Eiioos272!$ " 
 	
 **..
C/3.w.r   c                   i }	 | j                  d      }|j	                         D ]  }|j                         }|r|j                  d      r'|j                  d      r|t        d      d j                         }d|vrY|j                  d      \  }}}|j                         }|s|j                         }t        |      dk\  r|d   |d	   k(  r|d   d
v r|dd	 }|||<    |S # t        t        t        f$ r |cY S w xY w)ur  Parse a ``.env`` file into a plain dict WITHOUT touching ``os.environ``.

    Used to load a profile's secrets into an isolated mapping for
    ``set_secret_scope``. Mirrors python-dotenv's basic parsing (KEY=VALUE,
    ``export`` prefix, ``#`` comments, optional matching quotes) but never
    mutates the process environment — that isolation is the whole point.
    zutf-8)encoding#zexport N=   r   )'"   )
	read_textFileNotFoundErrorOSErrorUnicodeDecodeError
splitlinesstriprE   lenlstrip	partition)env_pathr   textrawlinekey_values           r   load_env_filerm      s    !G!!7!3   yy{ts+??9%I(//1Dd?s+Qiiku:?uQx594qZ9O!BKE" N) w(:; s   C% %C>=C>c                0    t        t        |       dz        S )u)  Build a profile's secret mapping from its ``<home>/.env``.

    Returns a fresh dict (safe to install via ``set_secret_scope``). Genuinely
    global vars are intentionally NOT copied in — ``get_secret`` reads those
    from ``os.environ`` directly, so the scope holds only profile secrets.
    z.env)rm   r   )hermes_homes    r   build_profile_secret_scoperp      s     k*V344r   )r   r
   returnNone)rq   r
   )r   Optional[Mapping[str, str]]rq   r   )r"   r   rq   rr   )rq   rs   )rH   strrq   r
   rD   )rH   rt   r   Optional[str]rq   ru   )rf   r   rq   Dict[str, str])ro   r   rq   rv   )r   
__future__r   rO   contextvarsr   r   pathlibr   typingr   r   r	   r   __annotations__r   r   r   RuntimeErrorr   r   r#   r&   	frozensetrJ   rL   rM   rS   rm   rp   r   r   r   <module>r~      s   , # 	 )  * *   4 % :DT:6 
, &
     A%/PD5r   