
    `gj&]                    >   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ZddlZddl	Z	ddl
mZ ddlmZmZmZmZ ddlmZmZmZmZ ddlmZmZ  ej2                  e      ZdZd	Z ej<                  d
      ZdZ ee!e!e!e!f   Z"i Z#de$d<   dZ%d'dZ& ee%e&      Z'de$d<   d(d)dZ(	 	 	 	 d*dZ)d+dZ*d,dZ+d-d.dZ,d/dZ-d0dZ.ddd	 	 	 	 	 	 	 	 	 d1dZ/dedddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d2d Z0ddeddddd!	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d3d"Z1 G d# d$e      Z2d4d%Z3d(d5d&Z4y)6u  1Password (`op` CLI) secret source.

Resolve provider credentials from 1Password ``op://vault/item/field``
references at process startup so they don't have to live in plaintext in
``~/.hermes/.env``.

Design summary
--------------

* Users map environment-variable names to official 1Password secret
  references in ``secrets.onepassword.env``::

      secrets:
        onepassword:
          enabled: true
          env:
            OPENAI_API_KEY: "op://Private/OpenAI/api key"
            ANTHROPIC_API_KEY: "op://Private/Anthropic/credential"

* After ``.env`` loads, each reference is resolved with a single
  ``op read -- <reference>`` call and injected into ``os.environ`` (the
  same point in startup as the Bitwarden source).
* Authentication is whatever the user's ``op`` CLI already uses — a
  service-account token (``OP_SERVICE_ACCOUNT_TOKEN``) for headless boxes,
  or a desktop/interactive session (``OP_SESSION_*``).  Hermes never
  authenticates on the user's behalf; it shells out to an already-trusted,
  already-authenticated CLI.
* Failures NEVER block startup.  A missing ``op`` binary, expired auth, a
  bad reference, or a permission error each surface a one-line warning and
  Hermes continues with whatever credentials ``.env`` already had.

The atomic-write / ``0600`` / TTL cache mechanics are shared with the other
backends via :mod:`agent.secret_sources._cache` — successful, complete pulls
are cached in-process and on disk under ``<hermes_home>/cache/op_cache.json``
so back-to-back short-lived ``hermes`` invocations don't re-shell ``op`` for
every reference.  The disk file holds only resolved secret *values*; auth
material is fingerprinted, never stored.
    )annotationsN)Path)DictListOptionalTuple)CachedFetch	DiskCacheFetchResultis_valid_env_name)	ErrorKindSecretSource   OP_SERVICE_ACCOUNT_TOKENz\x1b\[[0-?]*[ -/]*[@-~])PATHHOMEUSERPROFILEAPPDATALOCALAPPDATA
SystemRootTMPDIRTMPTEMPXDG_CONFIG_HOMEXDG_RUNTIME_DIR
OP_ACCOUNTOP_CONNECT_HOSTOP_CONNECT_TOKENzDict[_CacheKey, CachedFetch]_CACHEzop_cache.jsonc                $    | \  }}}}| d| d| S )a  Serialize a cache key for on-disk storage, omitting home_path.

    The disk file is already partitioned by home (it lives under
    ``<home>/cache/``), so the path provides the home dimension; folding it
    into the key string too would be redundant.
    | )	cache_keyauth_fpaccount_homerefs_fps        S/root/.hermes/venv/lib/python3.12/site-packages/agent/secret_sources/onepassword.py_disk_key_strr)   v   s)     (1$GWeWYay'++    )key_serializerr
   _DISK_CACHEc                ,    t         j                  |       S )zAPath to the on-disk cache (exposed for tests and direct callers).)r,   path	home_paths    r(   _disk_cache_pathr1      s    I&&r*   c                V   i }g }| xs i j                         D ]  \  }}t        |      s|j                  d|d       't        |t              s|j                  d|d       M|j                         }|j                  d      s|j                  d|d|d       |||<    ||fS )u  Return ``(valid_refs, warnings)`` from an ``env`` mapping.

    A reference is kept only if its target env-var name is a valid POSIX
    name and the value is a stripped ``op://…`` reference string.  Everything
    else produces a warning and is dropped (never fatal).
    z	Skipping z: not a valid env-var namez: reference is not a stringzop://: z! is not an op:// secret reference)itemsr   append
isinstancestrstrip
startswith)
referencesvalidwarningsnamerefcleaneds         r(   _validate_referencesr@      s     EH &B--/ 	c &OOix/IJK#s#OOix/JKL))+!!'*OOD82cW,MN d (?r*   c                   dt         j                  j                  | d       dt         j                  j                  dd       g}t        t         j                        D ];  }|j	                  d      s|j                  | dt         j                  |           = dj                  |      }t        j                  |j                  d            j                         d	d
 S )u  SHA-256 prefix over the auth material `op` would use.

    Folds in the service-account token, ``OP_ACCOUNT``, and *all*
    ``OP_SESSION_*`` vars (the names `op` actually exports for interactive
    sessions — ``OP_SESSION_<account_shorthand>``).  Signing out and into a
    different identity therefore changes the cache key, so a value cached under
    a previous identity is never served under a new one.  Never logged or
    displayed; the raw token never leaves this hash.
    ztoken= zaccount=r   OP_SESSION_=
utf-8N   )osenvirongetsortedr9   r5   joinhashlibsha256encode	hexdigest)	token_envpartskeymaterials       r(   _auth_fingerprintrU      s     	2./0
2::>>,345E bjj! 5>>-(LLC5"**S/!2345 yyH>>(//'23==?DDr*   c                     dj                   fdt               D              }t        j                  |j	                  d            j                         dd S )u<   SHA-256 prefix over the configured name→reference mapping.rE   c              3  2   K   | ]  }| d |      yw)rD   Nr"   ).0r=   r:   s     r(   	<genexpr>z$_refs_fingerprint.<locals>.<genexpr>   s"     U$D6:d#3"45Us   rF   NrG   )rL   rK   rM   rN   rO   rP   )r:   rT   s   ` r(   _refs_fingerprintrZ      sD    yyU&BTUUH>>(//'23==?DDr*   rB   c                    | rBt        |       }|j                         r&t        j                  |t        j                        r|S yt        j                  d      }|rt        |      S dS )u[  Resolve a usable ``op`` binary, or None.

    When ``binary_path`` is set it is used verbatim and PATH is NOT consulted
    — pinning an absolute path is a way to avoid trusting whatever ``op`` shows
    up first on ``PATH``.  A pinned-but-missing path returns None (the caller
    surfaces a clear error) rather than silently falling back.
    Nop)r   existsrH   accessX_OKshutilwhich)binary_pathpinnedfounds      r(   find_opre      sR     k"==?ryy9MLLE4;)T)r*   c                j    t         j                  d|       j                  dd      j                         S )zCRemove ANSI control sequences and trim, for safe message surfacing.rB   )_ANSI_CSI_REsubreplacer8   )texts    r(   _scrubrl      s+    B%--fb9??AAr*   c                    i }t         D ])  }t        j                  j                  |      }|%|||<   + t        j                  j	                         D ]  \  }}|j                  d      s|||<    | r| |d<   d|d<   |S )zEBuild a minimal allowlisted environment for the ``op`` child process.rC   r   1NO_COLOR)_OP_ENV_ALLOWLISTrH   rI   rJ   r4   r9   )token_valueenvrS   vals       r(   _op_child_envrt      s    C  jjnnS!?CH
 JJ$$& S>>-(CH
 *5&'C
OJr*   r%   rq   c          	     X   t        |       dg}|r|d|gz  }|d|gz  }	 t        j                  |t        |      ddddt              }|j                  dk7  rJt        |j                  xs d      d
d }|rt        d|d|       t        d|j                   d|      |j                  xs dj                  d      }|j                         st        d|      |S # t        j
                  $ r}t        dt         d	|      |d
}~wt        $ r}t        d|       |d
}~ww xY w)u   Resolve a single ``op://`` reference to its value.

    Raises :class:`RuntimeError` on any failure — including a ``returncode 0``
    with empty output, which would otherwise silently clobber a good
    ``.env``/shell credential with ``""``.
    readz	--accountz--TrF   rj   )rr   capture_outputrk   encodingerrorstimeoutzop read timed out after zs for Nzfailed to invoke op: r   rB      zop read failed for r3   zop read exited z for z
z$op read returned an empty value for )r7   
subprocessrunrt   _OP_RUN_TIMEOUTTimeoutExpiredRuntimeErrorOSError
returncoderl   stderrstdoutrstripr8   )	r\   	referencer%   rq   cmdprocexcerrvalues	            r(   _run_op_readr      s`    "gv&CW%% D)CC~~k*#
  !T[[&B'-!4YMC5IJJdoo.eI=A
 	
 [[B&&v.E;;=A)OPPL- $$ &&7vi]K
	  C23%89sBCs#   )C   D)3D		D)D$$D)T,  )r%   rQ   binaryrb   	use_cachecache_ttl_secondsr0   c                h   t        |       \  }}	|si |	fS t        j                  j                  |d      j	                         }
t        |      |xs d|t        |      ndt        |      f}|rxt        j                  |      }|r(|j                  |      rt        |j                        |	fS t        j                  |||      }| |t        |<   t        |j                        |	fS |xs t        |      }|t        d      i }d}t!        |      D ]  }	 t#        |||   ||
      ||<    |rM|sK|rIt'        t        |      t)        j(                               }|t        |<   t        j+                  ||||       ||	fS # t        $ r)}|	j%                  t        |             |dz  }Y d}~d}~ww xY w)u  Resolve ``references`` (name → ``op://…``) to ``(secrets, warnings)``.

    Raises :class:`RuntimeError` only when no ``op`` binary is available — a
    fatal "can't fetch anything" condition.  Per-reference failures (expired
    auth, bad reference, empty value) are collected as warnings and the
    reference is dropped, so one bad entry never sinks the rest.

    Only a complete, error-free pull is cached, so a transient auth failure
    isn't frozen in for the whole TTL window.
    rB   Nzop CLI not found.  Install the 1Password CLI (https://developer.1password.com/docs/cli/get-started/) or set secrets.onepassword.binary_path to its absolute location.r   ru      )secrets
fetched_at)r@   rH   rI   rJ   r8   rU   r7   rZ   r   is_freshdictr   r,   rw   re   r   rK   r   r5   r	   timewrite)r:   r%   rQ   r   rb   r   r   r0   r;   r<   rq   r#   cacheddisk_cachedr\   r   read_errorsr=   r   entrys                       r(   fetch_onepassword_secretsr   6  s   * +:6OE88|**..B/557K)$2#/IR% 	I I&foo&78'11!&&y2CYO" +F9++,h66		'7;'B	zH
 	
 !GKu 	(E$KkGDM DMdiikJ!y)U,=yIH  	OOCH%1K	s   E??	F1F,,F1)rr   r%   service_account_token_envrb   override_existingr   r0   c                   t               }| s|S t        |      \  }	}
|j                  j                  |
       i }|	j	                         D ]h  \  }}||k(  r|j
                  j                  |       '|s;t        j                  j                  |      r|j
                  j                  |       d|||<   j |s|S t        |      }||_        ||rd|d|_        |S d|_        |S 	 t        ||||||      \  }}||_        |j                  j                  |       |j	                         D ]  \  }}||k(  r*||j
                  vr|j
                  j                  |       5|sIt        j                  j                  |      r*||j
                  vr|j
                  j                  |       |t        j                  |<   |j"                  j                  |        |S # t        $ r}t        |      |_        |cY d}~S d}~ww xY w)u^  Resolve configured ``op://`` references and set them on ``os.environ``.

    Called by ``load_hermes_dotenv()`` after the .env files have loaded.
    Intentionally defensive — any failure returns a :class:`FetchResult` with
    ``error`` set (or surfaces warnings); it never raises.

    Parameters mirror the ``secrets.onepassword.*`` config keys so the caller
    can splat the dict in.  References that are already satisfied by the
    current environment (when ``override_existing`` is false) are skipped
    *before* fetching, so ``op`` is never invoked for a value that would be
    discarded.
    N!secrets.onepassword.binary_path (!) is not an executable op binary.secrets.onepassword.enabled is true but the op CLI was not found on PATH.  Install it (https://developer.1password.com/docs/cli/get-started/) or set secrets.onepassword.binary_path.r:   r%   rQ   r   r   r0   )r   r@   r<   extendr4   skippedr5   rH   rI   rJ   re   rb   errorr   r   r7   r   applied)enabledrr   r%   r   rb   r   r   r0   resultr;   r<   refs_to_fetchr=   r>   r   r   fetch_warningsr   r   s                      r(   apply_onepassword_secretsr     s   . ]F*3/OE8
OO8$ %'M[[] "	c,,NN!!$' RZZ^^D%9NN!!$'!d" [!FF~3K? C( ( L 3 L ";$//#
 FN
OO>*}} $e ,,6>>)%%d+ RZZ^^D%96>>)%%d+ 

4d#$ M+  3xs   &G 	H'G>8H>Hc                  @    e Zd ZdZdZdZdZdZddZddZ	ddZ
dd	Zy
)OnePasswordSourceu  1Password as a registered secret source.

    Thin adapter over the module's fetch machinery.  ``fetch()`` only
    *fetches* — precedence, override semantics, conflict warnings, and
    the ``os.environ`` writes are the orchestrator's job
    (see ``agent.secret_sources.registry.apply_all``).

    1Password is a **mapped** source: the user explicitly binds each env
    var to an ``op://`` reference under ``secrets.onepassword.env``, so
    its claims outrank bulk sources (e.g. a Bitwarden project dump) on
    contested vars.
    onepassword	1Passwordmappedr\   c                \    t        t        |t              xr |j                  dd            S )Nr   T)boolr6   r   rJ   )selfcfgs     r(   r   z#OnePasswordSource.override_existing  s'    
 JsD)Pcgg6I4.PQQr*   c                    t         }t        |t              rt        |j	                  d      xs |      }t        |h      S )Nr   )_DEFAULT_TOKEN_ENVr6   r   r7   rJ   	frozenset)r   r   rQ   s      r(   protected_env_varsz$OnePasswordSource.protected_env_vars   s7    &	c4 CGG$?@MINI)%%r*   c           	     H    ddddi dddddt         ddddd	d
dddddS )NzMaster switchF)descriptiondefaultz1Map of ENV_VAR -> op://vault/item/field referencez0op --account shorthand (empty = default account)rB   zOEnv var holding the service-account token (unset = desktop/interactive session)z,Pin the op binary (empty = resolve via PATH)z!Disk+memory cache TTL; 0 disablesr   z+Resolved values overwrite .env/shell valuesT)r   rr   r%   r   rb   r   r   )r   )r   s    r(   config_schemazOnePasswordSource.config_schema  se    '65IR
  R
 G-*  N
  C"
  M"/
 	
r*   c           	        t        |t              r|ni }t               }|j                  d      }t	        t        |t              r|nd       \  }}|j
                  j                  |       |s |sd|_        t        j                  |_
        |S t        |j                  d      xs d      }t        |      }||_        |,|rd|d|_        nd|_        t        j                  |_
        |S 	 t        |j                  dd	            }		 t%        |t        |j                  d      xs d      t        |j                  d      xs t&              ||	|      \  }
}|
|_        |j
                  j                  |       |S # t         t"        f$ r d
}	Y w xY w# t(        $ r5}t        |      |_        t+        t        |            |_
        |cY d }~S d }~ww xY w)Nrr   zksecrets.onepassword.enabled is true but the env: map is empty.  Add ENV_VAR: op://vault/item/field entries.rb   rB   r   r   r   r   r   g     r@r%   r   r   )r6   r   r   rJ   r@   r<   r   r   r   NOT_CONFIGURED
error_kindr7   re   rb   BINARY_MISSINGfloat	TypeError
ValueErrorr   r   r   _classify_op_errorr   )r   r   r0   r   env_mapr;   r<   rb   r   ttlr   r   r   s                r(   fetchzOnePasswordSource.fetch$  s   T*c''%..!'40Gd
x 	x(J  %.$<$<!M#''-06B7%#>7 G3 3 >  !* 8 8FM	 3S9:C	&? CGGI.4"5GG78N<N "%#	'#G^ !~.+ :& 	C	  	s8FL 23s8 <FM	s1   ,E< AF <FF	G*GGGN)r   r   returnr   )r   r   )r   r   )r   r   r0   r   r   r   )__name__
__module____qualname____doc__r=   labelshapeschemer   r   r   r   r"   r*   r(   r   r     s2     DEEFR&
<<r*   r   c                \   | j                         dv rt        j                  S dv sdv sdv rt        j                  S t	        fddD              rt        j
                  S dv rt        j                  S t	        fdd	D              rt        j                  S t        j                  S )
z@Best-effort mapping of op failure text onto the shared taxonomy.z	timed outznot found on pathznot an executablezfailed to invokec              3  &   K   | ]  }|v  
 y wNr"   rX   toklowereds     r(   rY   z%_classify_op_error.<locals>.<genexpr>k  s      5c3'> 5   )unauthorizedznot signed inzsession expiredauthentication401403zempty valuec              3  &   K   | ]  }|v  
 y wr   r"   r   s     r(   rY   z%_classify_op_error.<locals>.<genexpr>q  s      .c3'> .r   )network
connectionzresolve hostdns)	lowerr   TIMEOUTr   anyAUTH_FAILEDEMPTY_VALUENETWORKINTERNAL)messager   s    @r(   r   r   c  s    mmoGg   g%)<)G!W,'''
 5 &4 5 5 $$$$$$
 . &- . .   r*   c                V    t         j                          t        j                  |        y)zClear in-process AND disk caches.

    Tests can pass ``home_path`` to scope the disk cleanup to a tmpdir.
    Without it we fall back to the same default resolution as the writer.
    N)r   clearr,   r/   s    r(   _reset_cache_for_testsr   |  s     LLNi r*   )r#   	_CacheKeyr   r7   r   )r0   Optional[Path]r   r   )r:   Optional[Dict[str, str]]r    Tuple[Dict[str, str], List[str]])rQ   r7   r   r7   )r:   Dict[str, str]r   r7   )rB   )rb   r7   r   r   )rk   r7   r   r7   )rq   r7   r   r   )
r\   r   r   r7   r%   r7   rq   r7   r   r7   )r:   r   r%   r7   rQ   r7   r   r   rb   r7   r   r   r   r   r0   r   r   r   )r   r   rr   r   r%   r7   r   r7   rb   r7   r   r   r   r   r0   r   r   r   )r   r7   r   r   )r0   r   r   None)5r   
__future__r   rM   loggingrH   rer`   r}   r   pathlibr   typingr   r   r   r   agent.secret_sources._cacher	   r
   r   r   agent.secret_sources.baser   r   	getLoggerr   loggerr   r   compilerh   rp   r7   r   r   __annotations___DISK_CACHE_BASENAMEr)   r,   r1   r@   rU   rZ   re   rl   rt   r   r   r   r   r   r   r"   r*   r(   <module>r      sE  %N #   	 	     . .  >			8	$  0 
 rzz45 4 #sC$%	')$ )& , #Y 
'(%8E*E*,B
. 444 	4
 4 	4~ '!" $CC C 	C
 C C C C C &C\ %)%7"" $]] 
"] 	]
  #] ] ] ] ] ]Jz zz2!r*   