mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
MCP server config already resolves Cursor-style ${env:VAR} references
(mcp_tool._env_ref_name); config.yaml's expander treated the same shape
as a literal string — a confusing half-support. _expand_env_vars() now
strips the env: prefix and resolves identically, _env_ref_snapshot()
tracks the ref under the REAL var name (preserving the #58514 cache-
invalidation contract), and refs with a non-env source prefix
(bitwarden:/vault:/file:) warn with a pointer to the secrets: block
instead of being silently treated as a variable named 'bitwarden:FOO'.
Salvaged from PR #59516 — the audit-CLI half and the main() exit-code
change were out of scope and are not included.
Co-authored-by: andynguyendk <35395190+andynguyendk@users.noreply.github.com>
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
"""Config `${env:VAR}` SecretRef parity (salvaged from PR #59516).
|
|
|
|
`${env:VAR}` already resolved in MCP server config (mcp_tool._env_ref_name);
|
|
config.yaml's expander treated it as a literal. These tests pin the parity
|
|
plus the cache-snapshot tracking and the non-env-source warning behavior.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from hermes_cli.config import (
|
|
_env_ref_snapshot,
|
|
_env_ref_var_name,
|
|
_expand_env_vars,
|
|
)
|
|
|
|
|
|
def test_bare_ref_still_expands(monkeypatch):
|
|
monkeypatch.setenv("PARITY_VAR", "val-bare")
|
|
assert _expand_env_vars("x-${PARITY_VAR}-y") == "x-val-bare-y"
|
|
|
|
|
|
def test_env_prefixed_ref_expands(monkeypatch):
|
|
monkeypatch.setenv("PARITY_VAR", "val-prefixed")
|
|
assert _expand_env_vars("${env:PARITY_VAR}") == "val-prefixed"
|
|
|
|
|
|
def test_env_prefixed_ref_unset_stays_verbatim(monkeypatch):
|
|
monkeypatch.delenv("PARITY_MISSING", raising=False)
|
|
assert _expand_env_vars("${env:PARITY_MISSING}") == "${env:PARITY_MISSING}"
|
|
|
|
|
|
def test_empty_env_ref_stays_verbatim():
|
|
assert _expand_env_vars("${env:}") == "${env:}"
|
|
|
|
|
|
def test_non_env_source_stays_verbatim_with_warning(caplog):
|
|
import logging
|
|
with caplog.at_level(logging.WARNING, logger="hermes_cli.config"):
|
|
out = _expand_env_vars("${bitwarden:MY_KEY}")
|
|
assert out == "${bitwarden:MY_KEY}"
|
|
assert any("env:NAME" in r.message for r in caplog.records)
|
|
|
|
|
|
def test_nested_structures_expand(monkeypatch):
|
|
monkeypatch.setenv("PARITY_VAR", "v")
|
|
cfg = {"a": ["${env:PARITY_VAR}", {"b": "${PARITY_VAR}"}], "n": 3}
|
|
out = _expand_env_vars(cfg)
|
|
assert out == {"a": ["v", {"b": "v"}], "n": 3}
|
|
|
|
|
|
def test_value_containing_colon_is_not_a_source_ref(monkeypatch):
|
|
"""URL-ish or uppercase-colon refs are legacy bare names, not sources —
|
|
only a lowercase ident prefix counts as a SecretRef source."""
|
|
monkeypatch.delenv("MY:WEIRD", raising=False)
|
|
# Uppercase before ':' → treated as a bare (unset) var, kept verbatim,
|
|
# no misleading source warning.
|
|
assert _expand_env_vars("${MY:WEIRD}") == "${MY:WEIRD}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _env_ref_var_name + snapshot tracking
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize("ref,expected", [
|
|
("PLAIN_VAR", "PLAIN_VAR"),
|
|
("env:PLAIN_VAR", "PLAIN_VAR"),
|
|
("env: SPACED ", "SPACED"),
|
|
("env:", None),
|
|
("bitwarden:KEY", None),
|
|
("vault:path/to/key", None),
|
|
])
|
|
def test_env_ref_var_name(ref, expected):
|
|
assert _env_ref_var_name(ref) == expected
|
|
|
|
|
|
def test_snapshot_tracks_env_prefixed_under_real_name(monkeypatch):
|
|
monkeypatch.setenv("PARITY_SNAP", "s1")
|
|
snap = _env_ref_snapshot({"k": "${env:PARITY_SNAP}"})
|
|
assert snap == {"PARITY_SNAP": "s1"}
|
|
|
|
|
|
def test_snapshot_excludes_non_env_sources(monkeypatch):
|
|
snap = _env_ref_snapshot({"k": "${bitwarden:KEY}", "j": "${PARITY_SNAP2}"})
|
|
assert "bitwarden:KEY" not in snap
|
|
assert "KEY" not in snap
|
|
assert "PARITY_SNAP2" in snap
|
|
|
|
|
|
def test_snapshot_detects_rotation_for_env_prefixed(monkeypatch):
|
|
"""The #58514 cache-invalidation contract must hold for ${env:VAR} refs:
|
|
the snapshot records the value under the REAL var name, so a rotation
|
|
changes the snapshot."""
|
|
monkeypatch.setenv("PARITY_ROT", "before")
|
|
snap1 = _env_ref_snapshot({"k": "${env:PARITY_ROT}"})
|
|
monkeypatch.setenv("PARITY_ROT", "after")
|
|
snap2 = _env_ref_snapshot({"k": "${env:PARITY_ROT}"})
|
|
assert snap1 != snap2
|