fix(gateway): revoke deleted keystore-backed env vars on refresh

Force-refresh now also clears env vars that were previously injected by the
keystore but no longer exist in the current injectable secret set. This lets
credential deletion/revocation propagate in long-lived gateway processes
without restart, while still preserving external env precedence.

Adds a regression test covering deletion of a keystore-backed OPENAI_API_KEY
followed by gateway refresh.
This commit is contained in:
Shannon Sands
2026-03-27 09:42:49 +10:00
parent 5b16fa8621
commit 712bdfb949
2 changed files with 38 additions and 0 deletions
+12
View File
@@ -180,6 +180,18 @@ class KeystoreClient:
previous = dict(self._injected)
owned = _owned_env_names()
injected = {}
current_names = set(secrets.keys())
# Force-refresh also acts as revocation for previously keystore-owned
# env vars that have been deleted from the keystore or are no longer
# injectable. External env vars are never in `owned`, so they are not
# touched here.
if force:
removed = owned - current_names
for name in removed:
os.environ.pop(name, None)
owned -= removed
for name, value in secrets.items():
should_write = False
if name not in os.environ:
+26
View File
@@ -102,3 +102,29 @@ def test_gateway_refresh_does_not_clobber_external_env(monkeypatch, tmp_path):
ks.set_secret("OPENAI_API_KEY", "rotated-keystore-value")
gateway_run._inject_keystore_env(force=True)
assert os.environ.get("OPENAI_API_KEY") == "env-wins"
def test_gateway_refresh_revokes_deleted_keystore_secret(monkeypatch, tmp_path):
home = tmp_path / ".hermes"
home.mkdir(parents=True)
(home / ".env").write_text("")
(home / "config.yaml").write_text("toolsets:\n- hermes-cli\n")
monkeypatch.setenv("HERMES_HOME", str(home))
from keystore.client import KeystoreClient, reset_keystore
reset_keystore()
ks = KeystoreClient(home / "keystore" / "secrets.db")
ks.initialize("passphrase")
ks.set_secret("OPENAI_API_KEY", "sk-old")
monkeypatch.setenv("HERMES_KEYSTORE_PASSPHRASE", "passphrase")
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
gateway_run = _reload_gateway_run(monkeypatch, home)
assert os.environ.get("OPENAI_API_KEY") == "sk-old"
# Delete from keystore; force refresh should revoke the previously
# injected env var from the long-lived process.
ks.delete_secret("OPENAI_API_KEY")
gateway_run._inject_keystore_env(force=True)
assert os.environ.get("OPENAI_API_KEY") is None