Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions agent/secret_sources/bitwarden.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,35 @@ def _is_valid_env_name(name: str) -> bool:
return all(c.isalnum() or c == "_" for c in name)


def _active_profile_name(home_path: Optional[Path]) -> str:
"""Best-effort active profile name for profile-scoped secret aliases."""
if home_path is not None:
resolved = Path(home_path)
if resolved.parent.name == "profiles" and resolved.name:
return resolved.name
for env_name in ("HERMES_PROFILE_NAME", "HERMES_PROFILE"):
value = os.environ.get(env_name, "").strip()
if value and value != "default":
return value
return ""


def _profile_alias_target(key: str, *, home_path: Optional[Path]) -> Optional[str]:
"""Map ``FOO_<PROFILE>`` to ``FOO`` for the active profile when safe."""
profile = _active_profile_name(home_path)
if not profile:
return None
suffix = "_" + profile.replace("-", "_").upper()
if not key.endswith(suffix):
return None
alias = key[: -len(suffix)]
if not alias or not _is_valid_env_name(alias):
return None
if not any(alias.endswith(s) for s in ("_API_KEY", "_TOKEN", "_SECRET", "_KEY")):
return None
return alias


# ---------------------------------------------------------------------------
# Public entry point — called from hermes_cli.env_loader
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -657,18 +686,29 @@ def apply_bitwarden_secrets(
result.secrets = secrets
result.warnings.extend(warnings)

for key, value in secrets.items():
if key == access_token_env:
def _apply_env_var(env_name: str, value: str) -> bool:
if env_name == access_token_env:
# Don't let BSM clobber the very token we used to fetch
# itself — that would be a footgun if someone stored the
# token as a BSM secret too.
result.skipped.append(key)
continue
if not override_existing and os.environ.get(key):
result.skipped.append(key)
continue
os.environ[key] = value
result.applied.append(key)
result.skipped.append(env_name)
return False
if not override_existing and os.environ.get(env_name):
result.skipped.append(env_name)
return False
os.environ[env_name] = value
result.applied.append(env_name)
return True

for key, value in secrets.items():
_apply_env_var(key, value)

alias = _profile_alias_target(key, home_path=home_path)
if alias and alias not in secrets and _apply_env_var(alias, value):
result.warnings.append(
f"Applied profile-scoped secret {key} as {alias} for active profile "
f"{_active_profile_name(home_path)!r}"
)

return result

Expand Down
59 changes: 59 additions & 0 deletions tests/test_bitwarden_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,65 @@ def test_apply_does_not_override_existing(monkeypatch, tmp_path):
assert os.environ["NEW_KEY"] == "new-value"


def test_apply_profile_scoped_secret_aliases_to_unsuffixed_env(monkeypatch, tmp_path):
monkeypatch.setenv("BWS_ACCESS_TOKEN", "0.t")
monkeypatch.delenv("TELEGRAM_BOT_TOKEN", raising=False)
profile_home = tmp_path / ".hermes" / "profiles" / "milla"
profile_home.mkdir(parents=True)
fake_binary = tmp_path / "bws"
fake_binary.write_text("")
payload = _fake_bws_payload(
[{"key": "TELEGRAM_BOT_TOKEN_MILLA", "value": "123:profile-token"}]
)
monkeypatch.setattr(
bw.subprocess, "run",
lambda *a, **kw: mock.Mock(returncode=0, stdout=payload, stderr=""),
)
monkeypatch.setattr(bw, "find_bws", lambda **kw: fake_binary)

result = bw.apply_bitwarden_secrets(
enabled=True,
project_id="p",
auto_install=False,
home_path=profile_home,
)

assert result.ok
assert os.environ["TELEGRAM_BOT_TOKEN_MILLA"] == "123:profile-token"
assert os.environ["TELEGRAM_BOT_TOKEN"] == "123:profile-token"
assert "TELEGRAM_BOT_TOKEN_MILLA" in result.applied
assert "TELEGRAM_BOT_TOKEN" in result.applied


def test_apply_profile_scoped_alias_keeps_existing_unsuffixed_value(monkeypatch, tmp_path):
monkeypatch.setenv("BWS_ACCESS_TOKEN", "0.t")
monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "existing-token")
profile_home = tmp_path / ".hermes" / "profiles" / "milla"
profile_home.mkdir(parents=True)
fake_binary = tmp_path / "bws"
fake_binary.write_text("")
payload = _fake_bws_payload(
[{"key": "TELEGRAM_BOT_TOKEN_MILLA", "value": "123:profile-token"}]
)
monkeypatch.setattr(
bw.subprocess, "run",
lambda *a, **kw: mock.Mock(returncode=0, stdout=payload, stderr=""),
)
monkeypatch.setattr(bw, "find_bws", lambda **kw: fake_binary)

result = bw.apply_bitwarden_secrets(
enabled=True,
project_id="p",
auto_install=False,
home_path=profile_home,
)

assert result.ok
assert os.environ["TELEGRAM_BOT_TOKEN_MILLA"] == "123:profile-token"
assert os.environ["TELEGRAM_BOT_TOKEN"] == "existing-token"
assert "TELEGRAM_BOT_TOKEN" in result.skipped


def test_apply_override_existing(monkeypatch, tmp_path):
monkeypatch.setenv("BWS_ACCESS_TOKEN", "0.t")
monkeypatch.setenv("OPENAI_API_KEY", "stale")
Expand Down
Loading