From 75643a615405def3e73201e5b07fc7860343a2f6 Mon Sep 17 00:00:00 2001 From: Yuan Li Date: Wed, 20 May 2026 23:06:58 +0800 Subject: [PATCH] fix(env): strip null bytes from .env before python-dotenv loads Null bytes in API key values (introduced by copy-paste) crash os.environ[k] = v with ValueError: embedded null byte, preventing hermes from starting at all. --- hermes_cli/env_loader.py | 10 +++++++++- tests/hermes_cli/test_env_loader.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/hermes_cli/env_loader.py b/hermes_cli/env_loader.py index 8ef60f4e07f..0cc5f9ba3c0 100644 --- a/hermes_cli/env_loader.py +++ b/hermes_cli/env_loader.py @@ -140,6 +140,10 @@ def _sanitize_env_file_if_needed(path: Path) -> None: This produces mangled values — e.g. a bot token duplicated 8× (see #8908). + Also strips embedded null bytes which crash ``os.environ[k] = v`` + with ``ValueError: embedded null byte`` — typically introduced by + copy-pasting API keys from terminals or rich-text editors. + We delegate to ``hermes_cli.config._sanitize_env_lines`` which already knows all valid Hermes env-var names and can split concatenated lines correctly. @@ -155,7 +159,11 @@ def _sanitize_env_file_if_needed(path: Path) -> None: try: with open(path, **read_kw) as f: original = f.readlines() - sanitized = _sanitize_env_lines(original) + # Strip null bytes before _sanitize_env_lines so they never + # reach python-dotenv (which passes them to os.environ and + # crashes with ValueError). + stripped = [line.replace("\x00", "") for line in original] + sanitized = _sanitize_env_lines(stripped) if sanitized != original: import tempfile fd, tmp = tempfile.mkstemp( diff --git a/tests/hermes_cli/test_env_loader.py b/tests/hermes_cli/test_env_loader.py index f309dfd4c6a..2523754a84b 100644 --- a/tests/hermes_cli/test_env_loader.py +++ b/tests/hermes_cli/test_env_loader.py @@ -70,6 +70,23 @@ def test_user_env_takes_precedence_over_project_env(tmp_path, monkeypatch): assert os.getenv("OPENAI_API_KEY") == "project-key" +def test_null_bytes_in_user_env_are_stripped(tmp_path, monkeypatch): + home = tmp_path / "hermes" + home.mkdir() + env_file = home / ".env" + # Null bytes can be introduced when copy-pasting API keys. + env_file.write_text("GLM_API_KEY=abc\x00\x00\nOPENAI_API_KEY=sk-123\n", encoding="utf-8") + + monkeypatch.delenv("GLM_API_KEY", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + + loaded = load_hermes_dotenv(hermes_home=home) + + assert loaded == [env_file] + assert os.getenv("GLM_API_KEY") == "abc" + assert os.getenv("OPENAI_API_KEY") == "sk-123" + + def test_main_import_applies_user_env_over_shell_values(tmp_path, monkeypatch): home = tmp_path / "hermes" home.mkdir()