
    sj              
         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
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# ddl!m$Z$  ej%        e&          Z'd	Z(d
e( Z)de( dZ*dZ+dZ,ee-e-e-f         Z.i Z/de0d<   dZ1dZ2dZ3dZ4dndZ5 ee1e5          Z6de0d<   dodpd Z7dodpd!Z8dqd"Z9d#d$drd'Z:dsd(Z;dsd)Z<d#d*dtd,Z=dud0Z>dvd3Z?dwd5Z@dxd9ZAdyd<ZBdzd>ZCd{dAZDd|dCZEd}dFZFddGd~dJZGddGddNZHddOdPdQdd#ddRdd[ZIdd\ZJ	 ddd^ZKd_dQd#dOdPdQdd#dd`	ddfZL G dg dhe#          ZMddkZNdoddlZOdoddmZPdS )uB  Bitwarden Secrets Manager (`bws` CLI) integration.

Hermes pulls API keys from Bitwarden Secrets Manager at process startup
so they don't have to live in plaintext in ``~/.hermes/.env``.

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

* The ``bws`` binary is auto-installed into ``<hermes_home>/bin/bws`` on
  first use.  Hermes pins one version (``_BWS_VERSION``) and downloads
  the matching asset from the official GitHub Releases page, verifying
  the SHA-256 against the release's published checksum file.
* The access token is stored in ``~/.hermes/.env`` as
  ``BWS_ACCESS_TOKEN`` (or whatever name the user picked in
  ``secrets.bitwarden.access_token_env``).  This is the one
  bootstrap secret — every other provider key can live in Bitwarden.
* Pulling secrets is a single ``bws secret list <project_id>
  --output json`` call.  We cache the result in-process for
  ``cache_ttl_seconds`` so back-to-back ``hermes`` invocations don't
  hammer the API.
* Failures NEVER block Hermes startup.  Missing binary, no network,
  expired token, etc. all emit a one-line warning and continue with
  whatever credentials ``.env`` already had.

The module is intentionally subprocess-driven rather than going through
the ``bitwarden-sdk-secrets`` Python package: one cross-platform binary
is easier to lazy-install than a wheels-with-Rust-extension dependency.
    )annotationsN)Path)DictListOptionalTuple)CachedFetch	DiskCacheFetchResultis_valid_env_name)	ErrorKindSecretSource)get_source_environmentz2.0.0z;https://github.com/bitwarden/sdk-sm/releases/download/bws-vzbws-sha256-checksums-z.txt<      zDict[_CacheKey, _CachedFetch]_CACHEzbws_cache.jsonzbws_cache.enc.json   s   hermes-bws-encrypted-cache-v1	cache_key	_CacheKeyreturnstrc                "    | \  }}}| d| d| S )z:Serialize a cache key to a stable string for JSON storage.| )r   token_fp
project_id
server_urls       B/home/agent/.hermes/hermes-agent/agent/secret_sources/bitwarden.py_cache_key_strr   f   s*    '0$Hj*2222j222    )key_serializerr
   _DISK_CACHE	home_pathOptional[Path]r   c                6    t                               |           S )zReturn the disk cache path under hermes_home/cache/.

    Thin wrapper over the shared DiskCache, kept for tests and any direct
    callers; falls back to `$HERMES_HOME` / `~/.hermes` when home is None.
    )r"   pathr#   s    r   _disk_cache_pathr(   q   s     I&&&r    c                :    ddl m}  ||           dz  t          z  S )z>Return the encrypted disk cache path under hermes_home/cache/.r   )resolve_cache_homecache)agent.secret_sources._cacher*   _ENCRYPTED_CACHE_BASENAME)r#   r*   s     r   _encrypted_disk_cache_pathr.   z   s0    >>>>>>i((725NNNr    c                 (    ddl m}   |             dz  S )z9Where Hermes stores its managed binaries.  Profile-aware.r   get_hermes_homebin)hermes_constantsr1   r0   s    r   _hermes_bin_dirr4      s'    000000?u$$r    Finstall_if_missingr6   boolc                |   t                      t                      z  }|                                r!t          j        |t          j                  r|S t          j        d          }|rt          |          S | rB	 t                      S # t          $ r&}t                              d|           Y d}~dS d}~ww xY wdS )uR  Return a path to a usable ``bws`` binary, or None.

    Resolution order:
      1. ``<hermes_home>/bin/bws``  (our managed copy — preferred)
      2. ``shutil.which("bws")``    (system PATH)

    When ``install_if_missing`` is True and neither resolves, this calls
    :func:`install_bws` to download and verify the pinned version.
    bwszbws auto-install failed: %sN)r4   _platform_binary_nameexistsosaccessX_OKshutilwhichr   install_bws	Exceptionloggerwarning)r6   managedsystemexcs       r   find_bwsrH      s     "7"9"99G~~ BIgrw77 \%  F F|| 	==  	 	 	NN8#>>>44444	 4s   ;B	 	
B9B44B9c                 8    t          j                    dk    rdndS )NWindowszbws.exer9   )platformrF   r   r    r   r:   r:      s     ))Y6699EAr    c            	        t          j                    } t          j                                                    }| dk    rdt           dS | dk    r|dv rdnd}d| d	t           dS | d
k    r|dv rdnd}d}	 t          j        ddgdddddt
          j                  }d|j        |j	        z                                   v rd}n# t          t
          j        f$ r Y nw xY wd| d| dt           dS t          d|  d|           )u   Map (uname, arch, libc) → the upstream asset filename.

    Asset names follow Rust's target triple convention.  Linux defaults
    to gnu (glibc); we switch to musl only if ldd --version says so.
    Darwinzbws-macos-universal-z.ziprJ   )arm64aarch64rO   x86_64zbws-z-pc-windows-msvc-Linuxgnulddz	--versionTutf-8replace   )capture_outputtextencodingerrorstimeoutstdinmuslz-unknown-linux--z+Unsupported platform for bws auto-install:  )rK   rF   machinelower_BWS_VERSION
subprocessrunDEVNULLstdoutstderrOSErrorTimeoutExpiredRuntimeError)rF   r`   archlibcress        r   _platform_asset_namern      sq    _F  &&((G 9l8888#';;;yy?d??\????#';;;yy	.$#GI (  C #*sz188::::23 	 	 	D	DdDD4DD,DDDD
HfHHwHH  s   7AC CC)forcero   c           	     \   t                      }|                    dd           |t                      z  }|                                r| s|S t	                      }t
           d| }t
           dt           }t          j        d          5 }t          |          }||z  }|t          z  }	t                              d|           t          ||           t          ||	           t          |	|          }
t          |          }|
                                |                                k    rt!          d| d|
 d	|           t#          j        |          5 }t'          |t                                }t)          |||          }d
d
d
           n# 1 swxY w Y   t          j        t-          |          d          \  }}t/          j        |           t3          j        ||           t/          j        |t8          j        t8          j        z  t8          j        z  t8          j         z  t8          j!        z  t8          j"        z  t8          j#        z             t/          j$        ||           d
d
d
           n# 1 swxY w Y   t                              dtJ          |           |S )uZ  Download, verify, and install the pinned ``bws`` binary.

    Returns the path to the installed executable.  Raises on any
    failure (network, checksum, extraction) — callers in the auto-install
    path catch these; the user-facing ``hermes secrets bitwarden setup``
    surface lets them propagate so the wizard can show a clear error.
    Tparentsexist_ok/zhermes-bws-)prefixzDownloading %szChecksum mismatch for z: expected z, got Nz.bws_)dirru   zInstalled bws %s at %s)&r4   mkdirr:   r;   rn   _BWS_RELEASE_BASE_BWS_CHECKSUM_NAMEtempfileTemporaryDirectoryr   rC   info_http_download_expected_sha256_sha256_filera   rj   zipfileZipFile_pick_zip_member_safe_extract_membermkstempr   r<   closer?   copy2chmodstatS_IRUSRS_IWUSRS_IXUSRS_IRGRPS_IXGRPS_IROTHS_IXOTHrU   rb   )ro   bin_dirtarget
asset_name	asset_urlchecksum_urltmpdirtmpzip_pathchecksum_pathexpectedactualzfmember	extractedfdstageds                    r   rA   rA      s    GMM$M...,...F}} u %''J$33z33I'>>*<>>L		$M	:	:	: $#f6ll#00$i000y(+++|]333#M:>>h''>>v||~~--5 5 5$5 5,25 5  
 _X&& 	>"%b*?*A*ABBF
 -R==I	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> %#g,,wGGG
F
Y'''
L4<'$,6l!\*l!\*	
 	
 	
 	
66"""I$# $# $# $# $# $# $# $# $# $# $# $# $# $# $#L KK(,???Ms8   CJ .FJ F	J F	 CJ  JJurldestNonec                   t           j                            | ddi          }	 t           j                            |t                    5 }t          |d          5 }t          j        ||           d d d            n# 1 swxY w Y   d d d            d S # 1 swxY w Y   d S # t           j        j	        $ r}t          d|  d|           |d }~ww xY w)Nz
User-Agentzhermes-agent)headers)r[   wbzFailed to download : )urllibrequestRequesturlopen_BWS_DOWNLOAD_TIMEOUTopenr?   copyfileobjerrorURLErrorrj   )r   r   reqrespfrG   s         r   r}   r}     sd   
.
 
 |^.L
 
M
MCH^##C1F#GG 	,4dD!! ,Q"4+++, , , , , , , , , , , , , , ,	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, <  H H H=====>>CGHs_   &B# BA>2B>B	BB	B	B# BB# BB# #C7CCchecksum_filer   c                0   |                      dd          }|                                D ]Q}|                                                                }t	          |          dk    r|d         |k    r
|d         c S Rt          d| d| j                   )	zParse the upstream ``bws-sha256-checksums-X.Y.Z.txt`` file.

    Format is the standard ``sha256sum`` output: ``<hex>  <filename>``,
    one per line.
    rT   rU   )rY   rZ   rV   r   zNo checksum entry for z in )	read_text
splitlinesstripsplitlenrj   name)r   r   rX   linepartss        r   r~   r~     s     ""GI"FFD!!  

""$$u::??uRyJ668OOO
EEE1CEE  r    r&   c                    t          j                    }t          | d          5 t          fdd          D ]}|                    |           	 d d d            n# 1 swxY w Y   |                                S )Nrbc                 .                          d          S )Ni   )read)r   s   r   <lambda>z_sha256_file.<locals>.<lambda>1  s    !&&-- r    r    )hashlibsha256r   iterupdate	hexdigest)r&   hchunkr   s      @r   r   r   .  s    A	dD		 Q////55 	 	EHHUOOOO	               ;;==s   ,AA"%A"r   zipfile.ZipFilebinary_namec                    fd|                                  D             }|s0t          d d|                                  dd          d          |                    t                     |d         S )	zFind the binary inside the upstream zip.

    Historically the archive has been flat (``bws`` at the root) but we
    tolerate a top-level directory just in case upstream changes.
    c                R    g | ]#}|                     d           d         k    !|$S )rt   r   )r   ).0nr   s     r   
<listcomp>z$_pick_zip_member.<locals>.<listcomp><  s2    NNNaggcll2.>+.M.M!.M.M.Mr    zCould not find z% inside downloaded archive (members: N   z...))keyr   )namelistrj   sortr   )r   r   
candidatess    ` r   r   r   6  s     ONNNR[[]]NNNJ 
1k 1 1rr*1 1 1
 
 	

 OOOa=r    r   dest_dirc                   t           j                            |          }t           j                            t           j                            ||                    }	 t           j                            ||g          |k    }n# t
          $ r d}Y nw xY w|r||k    rt          d|d          |                     ||           t          |          S )aM  Extract a single archive member, refusing path traversal.

    ``ZipFile.extract`` will happily honour member names containing
    ``../`` or absolute paths, letting a malicious archive write outside
    ``dest_dir`` (a "zip-slip").  We resolve the would-be target and
    confirm it stays within ``dest_dir`` before extracting.
    Fz*Refusing to extract unsafe archive member z%: it escapes the extraction directory)	r<   r&   realpathjoin
commonpath
ValueErrorrj   extractr   )r   r   r   	dest_rootr   	containeds         r   r   r   G  s       **IWbgll9f==>>FG&&	6':;;yH		   			 
)++3 3 3 3
 
 	
 JJvy!!!<<s   %B BBtokenc                    t          j        |                     d                                                    dd         S )uE   SHA-256 prefix used as a cache key — never logged, never displayed.rT   N   )r   r   encoder   )r   s    r   _token_fingerprintr   g  s3    >%,,w//00::<<SbSAAr    rawbytesc                P    t          j        |                               d          S )Nascii)base64	b64encodedecode)r   s    r   _b64er   l  s!    C  ''000r    rX   c                T    t          j        |                     d          d          S )Nr   T)validate)r   	b64decoder   )rX   s    r   _b64dr   p  s$    DKK004@@@@r    access_tokensaltc                    ddl m} ddlm}  ||                                d|t
                                        |                     d                    S )zCDerive the local cache encryption key from the bootstrap BWS token.r   )hashes)HKDF    )	algorithmlengthr   r|   rT   )cryptography.hazmat.primitivesr   'cryptography.hazmat.primitives.kdf.hkdfr   SHA256_ENCRYPTED_CACHE_INFOderiver   )r   r   r   r   s       r   _derive_encrypted_cache_keyr   t  sr     655555<<<<<<4--//"	  
 f\  ))**+r    r'   entry_CachedFetchc                   t          |          }	 ddlm} |j        }|                    dd           	 t          j        |d           n# t          $ r Y nw xY wt          j        d          }t          j        d          }t          |           }	t          ||          }
t          j        |j        |j        dd	
                              d          } ||
                              |||	                    d                    }t"          |	t%          |          t%          |          t%          |          d}t'          j        ddt+          |                    \  }}	 t          j        |dd          5 }t          j        ||           ddd           n# 1 swxY w Y   t          j        |d           t          j        ||           	 t3          |                                           n# t6          $ r Y nt          $ r Y nw xY wdS dS dS # t8          $ r( 	 t          j        |           n# t          $ r Y nw xY w w xY w# t:          $ r Y dS w xY w)zPersist an encrypted last-good cache entry atomically.

    Best-effort by design: cache write failure must never block a fresh BWS
    fetch.  The raw BWS access token is not stored; it only derives the AES key.
    r   AESGCMTrq   i  r      secrets
fetched_at),:)
separatorsrT   )versionr   r   nonce
ciphertextz.bws_cache_enc_z.tmp)ru   suffixrv   wrY   Ni  )r.   +cryptography.hazmat.primitives.ciphers.aeadr  parentrw   r<   r   rh   urandomr   r   jsondumpsr  r  r   encrypt_ENCRYPTED_CACHE_VERSIONr   rz   r   r   fdopendumprU   r(   unlinkFileNotFoundErrorBaseExceptionrB   )r   r   r   r#   r&   r  	cache_dirr   r  serialized_keyr   	plaintextr  payloadr   r   r   s                    r   _write_encrypted_disk_cacher     s    &i00D2FFFFFFK	t444	HY&&&& 	 	 	D	z"~~
2'	22),==JU5EFF!
 
 
 &// 	 VC[[((9n33G<<
 

 0!$KK5\\
++
 
 "$VY
 
 
C	2sW555 &	'1%%%& & & & & & & & & & & & & & &HS%   JsD!!! ++224444$       54 	 	 		#   	    s   $I A I 
AI ADI (H ?F!H !F%%H (F%)-H !G9 8H 9
HH 	HH HH 
I%H:9I:
IIIII 
IImax_age_secondsfloatOptional[_CachedFetch]c                (   |dk    rdS t          |          }	 ddlm} t          j        |                    d                    }t          |t                    sdS t          |           }|	                    d          t          k    rdS |	                    d          |k    rdS t          t          |	                    dd	                              }t          t          |	                    d
d	                              }	t          t          |	                    dd	                              }
t          ||          } ||                              |	|
|                    d                    }t          j        |                    d                    }t          |t                    sdS |	                    d          }|	                    d          }t          |t                    rt          |t"          t$          f          sdS t'          j                    t%          |          z
  }|dk     s||k    rdS d |                                D             }t+          |t%          |                    S # t,          $ r Y dS w xY w)zHReturn a decrypted encrypted-cache entry if it matches and is in-window.r   Nr  rT   r  r
  r   r    r  r  r  r  c                n    i | ]2\  }}t          |t                    t          |t                    /||3S r   )
isinstancer   )r   kvs      r   
<dictcomp>z._read_encrypted_disk_cache.<locals>.<dictcomp>  sP     
 
 
Q!S!!
&0C&8&8
q
 
 
r    r  )r.   r  r  r  loadsr   r'  dictr   getr  r   r   r   decryptr   r   intr"  timeitemsr   rB   )r   r   r!  r#   r&   r  r  r  r   r  r  r   r   innerr  inner_fetched_at	entry_agetypeds                     r   _read_encrypted_disk_cacher6    sx    !t%i00D"FFFFFF*T^^W^==>>'4(( 	4'	22;;y!!%===4;;u//4SVR001122c'++gr2233443w{{<<<==>>
),==fSkk!!:~44W==
 
 
3::g..//%&& 	4))I&& 99\22'4(( 	
;KcSX\0Z0Z 	4IKK%(8"9"99	q==I774
 
$]]__
 
 
 Ee<L6M6MNNNN   tts8   AJ -J J (DJ 9AJ /J ;J 
JJ,  Tr%  )binarycache_ttl_seconds	use_cacher   r#   encrypted_cache_enabled!encrypted_cache_max_stale_secondsr   r8  r9  r:  r   r;  r<   Tuple[Dict[str, str], List[str]]c        	        \   | st          d          |st          d          t          |           ||pdf}	|r|dk    rt                              |	          }
|
r|
                    |          r	|
j        g fS |rt          |	| ||          }nt                              |	||          }||t          |	<   |j        g fS |pt          d          }|t          d	          	 t          || ||          \  }}nB# t           $ r4}t          t          |                    }|r|t          j        t          j        fv r|rlt          |	| ||          }|Vt!          d
t#          j                    |j        z
            }|t          |	<   |j        d| dt'          |           dgfcY d}~S n|dk    rt                              |	t)          d          |          }|Vt!          d
t#          j                    |j        z
            }|t          |	<   |j        d| dt'          |           dgfcY d}~S  d}~ww xY wt+          |t#          j                              }|rI|dk    r
|t          |	<   |rt-          |	| ||           n#|dk    rt                              |	|||           ||fS )u  Pull the secrets for ``project_id`` from Bitwarden Secrets Manager.

    Returns ``(secrets_dict, warnings_list)``.

    Set ``server_url`` to point at a non-default Bitwarden region or a
    self-hosted instance — e.g. ``https://vault.bitwarden.eu`` for EU
    Cloud accounts.  When empty, ``bws`` uses its built-in default
    (``https://vault.bitwarden.com``, US Cloud).  This is plumbed into
    the subprocess as ``BWS_SERVER_URL``.

    ``cache_ttl_seconds`` controls the normal fresh cache.  When
    ``encrypted_cache_enabled`` is true, fresh cache entries are written as
    AES-GCM encrypted JSON instead of plaintext, and a last-good encrypted
    entry may be used after NETWORK/TIMEOUT failures for up to
    ``encrypted_cache_max_stale_seconds``.  This stale fallback is separate
    from the fresh-cache TTL so operators can set ``cache_ttl_seconds: 0``
    while still keeping an encrypted break-glass cache for offline startup.

    Raises :class:`RuntimeError` for fatal conditions (missing binary,
    auth failure, unparseable output).  Callers in the env_loader path
    catch this and emit a single warning; callers in the user-facing
    setup wizard let it propagate.
    zBitwarden access token is emptyzBitwarden project_id is emptyr%  r   )r   r   r!  r#   NTr5   u   bws binary not available — auto-install failed and `bws` is not on PATH.  Install manually from https://github.com/bitwarden/sdk-sm/releases or re-run `hermes secrets bitwarden setup`.        zbws live fetch failed (z/); falling back to stale ENCRYPTED disk cache (zs old)infz%); falling back to stale disk cache (r  )r   r   r   r#   )rj   r   r   r-  is_freshr  r6  r"   r   rH   _run_bws_list_classify_bws_errorr   r   NETWORKTIMEOUTmaxr0  r  r/  r"  r   r   write)r   r   r8  r9  r:  r   r#   r;  r<  r   cacheddisk_cachedr9   r  warningsrG   kindstaleager   s                       r   fetch_bitwarden_secretsrN    s   F  ><=== <:;;;#L11:z?ORPI +&**I&& 	&foo&788 	&>2%%" 	T4#) 1#	  KK &**96GSSK" !,F9&**

5H555C
{0
 
 	
0)#|ZTT . . .* #3s88,, 	)"3Y5F!GGG& 2'!-$E'	   $c49;;1A#ABBC(-F9% =H# H H7:3xxH H H+        % #Q&&#((E%LL)LL$c49;;1A#ABBC(-F9% =N# N N=@XXN N N+        	].^ TY[[AAAE Nq   %F9" 	N (#)#	     ""i0A9MMMHs2   $C; ;H:B H5&H:,BH5.H:4H55H:c                x   |                      dd                                          }|s|S g }|                                D ]_}|                                }|                    d          r n3|dv r2t	          j        dd|          }|r|                    |           `|rd                    |          n|S )a  Reduce a bws (Rust color-eyre) error dump to its cause line(s).

    bws failures look like::

        Error:
           0: Received error message from server: [400 Bad Request] {"error":"invalid_client"}

        Location:
           crates/bws/src/main.rs:108
        ...

    Everything from ``Location:`` on is diagnostic noise for a Hermes
    user.  Keep the numbered cause lines (joined), drop the rest, and
    fall back to the stripped raw text when the shape is unrecognized.
    r%  )z	Location:zBacktrace omittedz	Run with )r%  zError:z^\d+:\s*z; )rU   r   r   
startswithresubappendr   )r   rX   causesr   strippeds        r   _summarize_bws_stderrrW    s      ;;vr""((**D F!! 	$ 	$::<<NOO 	E~%%6+r844 	$MM(### &0499VD0r    r9   c           
        t          |           dd|ddg}t                      }|t          j        u rddlm}  |dd          }nt          |          }||d	<   |                    d
d           |r||d<   	 t          j	        ||ddddt          t          j                  }nP# t          j        $ r}	t          dt           d          |	d }	~	wt          $ r}	t          d|	           |	d }	~	ww xY w|j        dk    r?t!          |j        p|j        pd          }
t          d|j         d|
d d                    |j                                        }|si dgfS 	 t)          j        |          }n*# t(          j        $ r}	t          d|	           |	d }	~	ww xY wt/          |t0                    s$t          dt3          |          j                   i }g }|D ]}t/          |t                    s|                    d          }|                    d          }t/          |t                     rt/          |t                     smt9          |          s|                    d|d           |||<   ||fS )Nsecretlistz--outputr  r   )build_subprocess_envF)scrub_secretsinherit_profile_homeBWS_ACCESS_TOKENNO_COLOR1BWS_SERVER_URLTrT   rU   )envrW   rX   rY   rZ   r[   r\   zbws timed out after zs fetching secretszfailed to invoke bws: r%  zbws exited r      z'bws returned no output (empty project?)zbws returned non-JSON output: zbws returned unexpected shape: r   valuezSkipping secret z: not a valid env-var name)r   r   r<   environtools.environments.localr[  r,  
setdefaultrc   rd   _BWS_RUN_TIMEOUTre   ri   rj   rh   
returncoderW  rg   rf   r   r  r+  JSONDecodeErrorr'  rZ  type__name__r-  _is_valid_env_namerT  )r9   r   r   r   cmd
source_envr[  rb  procrG   errr   r  r  rJ  itemr   rd  s                     r   rB  rB    s.    s88Xvz:v
FC ())JRZAAAAAA""USSS:*CNN:s###  + *D~	$$
 
 
 $   G#3GGG
 
	  D D D9C99::CD !
 $DK$D4;$D"EE8$/88S#Y88
 
 	
 +



C ?=>>>L*S// L L LACAABBKL gt$$ 
Fd7mm.DFF
 
 	
 !GH  $%% 	hhuoo!!#s## 	:eS+A+A 	!#&& 	OOD3DDD   Hs<   8+B$ $C13CC1C,,C1E4 4FFFr^  )	access_token_envr   override_existingr9  auto_installr   r#   r;  r<  enabledrs  rt  ru  r   c        
   
        t                      }
| s|
S t          j                            |d                                          }|sd| d|
_        |
S |s	d|
_        |
S t          |          }||
_        |	d|
_        |
S 	 t          ||||||||	          \  }}n-# t          $ r }t          |          |
_        |
cY d}~S d}~ww xY w||
_        |
j                            |           |                                D ]\  }}||k    r|
j                            |           &|s:t          j                            |          r|
j                            |           b|t          j        |<   |
j                            |           |
S )	u8  Pull secrets from BSM and set them on ``os.environ``.

    This is the function ``load_hermes_dotenv()`` calls after the .env
    files have loaded.  It is intentionally defensive — any failure
    returns a :class:`FetchResult` with ``error`` set; it never raises.

    ``server_url`` selects the Bitwarden region or self-hosted endpoint
    (e.g. ``https://vault.bitwarden.eu`` for EU Cloud).  Empty string
    means use ``bws``'s default (US Cloud).

    Parameters mirror the ``secrets.bitwarden.*`` config keys so the
    caller can just splat the dict in.
    r%  &secrets.bitwarden.enabled is true but 3 is not set.  Run `hermes secrets bitwarden setup`.Msecrets.bitwarden.project_id is empty.  Run `hermes secrets bitwarden setup`.r5   Nhbws binary not available and auto-install is disabled.  Run `hermes secrets bitwarden setup` to install.r   r   r8  r9  r   r#   r;  r<  )r   r<   re  r-  r   r   rH   binary_pathrN  rj   r   r  rJ  extendr1  skippedrT  applied)rv  rs  r   rt  r9  ru  r   r#   r;  r<  resultr   r8  r  rJ  rG   r   rd  s                     r   apply_bitwarden_secretsr    s   4 ]]F :>>"2B77==??L >5E > > > 	  4 	 666FF~? 	 3%!/!$;.O	
 	
 	
    3xx FN
O8$$$mmoo # #
U""" N!!#&&&  	RZ^^C%8%8 	N!!#&&&
3c""""Ms   B 
C'C<CCc                  T     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d fdZ xZS )BitwardenSourceu   Bitwarden Secrets Manager 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``).

    Bitwarden is a **bulk** source: it injects every secret in the
    configured BSM project, so explicit per-var bindings from mapped
    sources (e.g. the 1Password ``env:`` map) outrank it.
    	bitwardenzBitwarden Secrets Managerbulkr9   cfgr,  r   r7   c                r    t          t          |t                    o|                    dd                    S )Nrt  T)r7   r'  r,  r-  )selfr  s     r   rt  z!BitwardenSource.override_existingi  s0    
 JsD))Pcgg6I4.P.PQQQr    c                    d}t          |t                    r$t          |                    d          p|          }t	          |h          S )Nr^  rs  )r'  r,  r   r-  	frozenset)r  r  	token_envs      r   protected_env_varsz"BitwardenSource.protected_env_varsp  sI    &	c4   	FCGG$677D9EEI)%%%r    c           
     N    ddddddddddd	dd
ddddddddddddddS )NzMaster switchF)descriptiondefaultz0Env var holding the machine-account access tokenr^  zBSM project UUIDr%  z9Fresh disk+memory cache TTL; 0 disables fresh-cache reuser7  z6Encrypted last-good cache for network/timeout fallbackr   )rv  max_stale_secondsz&BSM values overwrite .env/shell valuesTz#Auto-download the pinned bws binaryz0Region / self-hosted endpoint (empty = US Cloud))rv  rs  r   r9  encrypted_cachert  ru  r   r   )r  s    r   config_schemazBitwardenSource.config_schemav  s    '65IIQ-! ! +=LLZ" "
  X$)*      H" "
  E 
  R 5
 
 	
r    r#   r   r   c                   t          |t                    r|ni }t                      }t          |                    d          pd          }t                                          |d                                          }|sd| d|_        t          j	        |_
        |S t          |                    d          pd          }|sd|_        t          j	        |_
        |S t          |                    dd	                    }t          |
          }||_        |d|_        t          j        |_
        |S 	 t          |                    dd                    }	n# t           t"          f$ r d}	Y nw xY w|                    d          }
t          |
t                    r|
ni }
t          |
                    dd                    }	 t          |
                    dd                    }n# t           t"          f$ r d}Y nw xY w	 t%          ||||	t          |                    dd          pd                                          |||          \  }}nv# t&          $ ri}t          |          |_        t)          t          |                    |_
        |j
        t          j        k    rd| d|j         d|_        |cY d }~S d }~ww xY w||_        |j                            |           |S )Nrs  r^  r%  rx  ry  r   rz  ru  Tr5   r{  r9  r7  g     r@r  rv  Fr  r   r?  r   r|  z5Bitwarden rejected the machine-account access token (uF   ) — it was likely revoked, expired, or belongs to another region.  ())r'  r,  r   r   r-  r   r   r   r   NOT_CONFIGURED
error_kindr7   rH   r}  BINARY_MISSINGr"  	TypeErrorr   rN  rj   rC  AUTH_FAILEDr  rJ  r~  )r  r  r#   r  rs  r   r   ru  r8  ttlencrypted_cfgencrypted_enabledencrypted_max_staler  rJ  rG   s                   r   fetchzBitwardenSource.fetch  sH   T**2ccsww'9::P>PQQ-//334DbIIOOQQ 	B9I B B B L !* 8FM..4"55
 	8 L !* 8FMCGGND99::\:::#>C L !* 8FM	 3S99::CC:& 	 	 	CCC	  122)3M4)H)HPb !2!29e!D!DEE	&"'(9(9:Mq(Q(Q"R"R:& 	& 	& 	&"%	&	 7)%"%sww|R88>B??EEGG#(92E	! 	! 	!GXX  	 	 	s88FL 3CHH = =F I$999G(G G7=|G G G 
 MMMMMM	 !x(((sD   7#E E10E1#G* *H ?H AI 
KAK<KKr   c                    |t           j        t           j        fv r	 dS t                                          ||          S )Nu   Run `hermes secrets bitwarden token` to paste a fresh access token (create one in the Bitwarden web app: Secrets Manager → Machine accounts → Access tokens).  Wrong region?  Re-run `hermes secrets bitwarden setup` and pick EU/self-hosted.)r   r  AUTH_EXPIREDsuperremediation)r  rK  r  	__class__s      r   r  zBitwardenSource.remediation  sE    I)9+ABBBL  ww""4---r    )r  r,  r   r7   )r  r,  )r   r,  )r  r,  r#   r   r   r   )r  r,  r   r   )rl  
__module____qualname____doc__r   labelshapeschemert  r  r  r  r  __classcell__)r  s   @r   r  r  W  s        
 
 D'EEFR R R R& & & &
 
 
 
BI I I IV. . . . . . . . . .r    r  messager   c                (   |                                  dv rt          j        S dv sdv rt          j        S t	          fddD                       rt          j        S t	          fddD                       rt          j        S t          j        S )zABest-effort mapping of bws failure text onto the shared taxonomy.z	timed outzbinary not availablezfailed to invokec              3      K   | ]}|v V  	d S Nr   r   toklowereds     r   	<genexpr>z&_classify_bws_error.<locals>.<genexpr>  s7       : :c3'> : : : : : :r    )unauthorizedzinvalid tokenzaccess token401403invalid_clientinvalid_grantz400 bad requestc              3      K   | ]}|v V  	d S r  r   r  s     r   r  z&_classify_bws_error.<locals>.<genexpr>  s7       : :c3'> : : : : : :r    )network
connectionresolvedownloaddns)ra   r   rE  r  anyr  rD  INTERNAL)r  r  s    @r   rC  rC    s    mmooGg  ((,>',I,I''
 : : : : &9 : : : : : % $$
 : : : : &9 : : : : : !  r    c                    t                                            t                              |            	 t          |                                            dS # t
          t          f$ r Y dS w xY w)aY  Drop in-process AND disk caches (plaintext and encrypted).

    Used after a token rotation (`hermes secrets bitwarden token`) so the
    next startup fetches fresh with the new credential instead of serving
    a pull cached under the old token's fingerprint.  The encrypted cache
    is keyed off the old token too, so it must go as well.
    N)r   clearr"   r.   r  r  rh   r'   s    r   clear_cachesr    sq     LLNNNi   "9--4466666w'   s   !A A-,A-c                $    t          |            dS )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 cache
    writer itself.
    N)r  r'   s    r   _reset_cache_for_testsr    s     r    )r   r   r   r   r  )r#   r$   r   r   )r   r   )r6   r7   r   r$   )r   r   )ro   r7   r   r   )r   r   r   r   r   r   )r   r   r   r   r   r   )r&   r   r   r   )r   r   r   r   r   r   )r   r   r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   )rX   r   r   r   )r   r   r   r   r   r   )
r   r   r   r   r   r   r#   r$   r   r   )
r   r   r   r   r!  r"  r#   r$   r   r#  )r   r   r   r   r8  r$   r9  r"  r:  r7   r   r   r#   r$   r;  r7   r<  r"  r   r=  )r   r   r   r   )r%  )
r9   r   r   r   r   r   r   r   r   r=  )rv  r7   rs  r   r   r   rt  r7   r9  r"  ru  r7   r   r   r#   r$   r;  r7   r<  r"  r   r   )r  r   r   r   )r#   r$   r   r   )Qr  
__future__r   r   r   r  loggingr<   rK   rR  r?   r   rc   rz   r0  urllib.errorr   urllib.requestr   pathlibr   typingr   r   r   r   r,   r	   r   r
   r   r   rm  agent.secret_sources.baser   r   r   	getLoggerrl  rC   rb   rx   ry   r   rh  r   r   r   __annotations___DISK_CACHE_BASENAMEr-   r  r   r   r"   r(   r.   r4   rH   r:   rn   rA   r}   r~   r   r   r   r   r   r   r   r   r6  rN  rW  rB  r  r  rC  r  r  r   r    r   <module>r     s*    : # " " " " "     				  				                        . . . . . . . . . . . .            > = = = = = = = < < < < < <		8	$	$  Q,PP  @\???     #sC- 	(* * * * * ( 0  8 3 3 3 3 #      
' ' ' ' 'O O O O O% % % % ,1      6B B B B( ( ( (V "' : : : : : :zH H H H          "   @B B B B
1 1 1 1A A A A+ + + +, !%? ? ? ? ? ?N !%- - - - - -h "" $$)/0G G G G G GT1 1 1 1D FHO O O O Ot /#" $$)/0V V V V V V|S. S. S. S. S.l S. S. S.l   6           r    