fix(env_loader): warn when non-ASCII stripped from credential env vars (#13300)

Load-time sanitizer silently removed non-ASCII codepoints from any
env var ending in _API_KEY / _TOKEN / _SECRET / _KEY, turning
copy-paste artifacts (Unicode lookalikes, ZWSP, NBSP) into opaque
provider-side API_KEY_INVALID errors.

Warn once per key to stderr with the offending codepoints (U+XXXX)
and guidance to re-copy from the provider dashboard.
This commit is contained in:
Teknium
2026-04-20 22:14:03 -07:00
committed by GitHub
parent 5125a78283
commit fdd0ecaf13
2 changed files with 102 additions and 3 deletions

View File

@@ -54,15 +54,17 @@ class TestEnvLoaderSanitization:
"""Tests for _sanitize_loaded_credentials in env_loader."""
def test_strips_non_ascii_from_api_key(self, monkeypatch):
from hermes_cli.env_loader import _sanitize_loaded_credentials
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
_WARNED_KEYS.discard("OPENROUTER_API_KEY")
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-proj-abcʋdef")
_sanitize_loaded_credentials()
assert os.environ["OPENROUTER_API_KEY"] == "sk-proj-abcdef"
def test_strips_non_ascii_from_token(self, monkeypatch):
from hermes_cli.env_loader import _sanitize_loaded_credentials
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
_WARNED_KEYS.discard("DISCORD_BOT_TOKEN")
monkeypatch.setenv("DISCORD_BOT_TOKEN", "tokénvalue")
_sanitize_loaded_credentials()
assert os.environ["DISCORD_BOT_TOKEN"] == "toknvalue"
@@ -81,3 +83,51 @@ class TestEnvLoaderSanitization:
monkeypatch.setenv("OPENAI_API_KEY", "sk-proj-allascii123")
_sanitize_loaded_credentials()
assert os.environ["OPENAI_API_KEY"] == "sk-proj-allascii123"
def test_warns_to_stderr_when_stripping(self, monkeypatch, capsys):
"""Silent stripping masks bad keys as opaque provider 400s (see #6843 fallout).
Users must be told when a copy-paste artifact was removed so they
can re-copy the key if authentication fails.
"""
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
_WARNED_KEYS.discard("GOOGLE_API_KEY")
monkeypatch.setenv("GOOGLE_API_KEY", "AIzaSy\u200babcdef") # ZWSP mid-key
_sanitize_loaded_credentials()
assert os.environ["GOOGLE_API_KEY"] == "AIzaSyabcdef"
captured = capsys.readouterr()
assert "GOOGLE_API_KEY" in captured.err
assert "U+200B" in captured.err
assert "re-copy" in captured.err.lower()
def test_warning_fires_only_once_per_key(self, monkeypatch, capsys):
"""Repeated loads (user env + project env) must not double-warn."""
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
_WARNED_KEYS.discard("GEMINI_API_KEY")
monkeypatch.setenv("GEMINI_API_KEY", "AIza\u028bbad")
_sanitize_loaded_credentials()
first = capsys.readouterr().err
monkeypatch.setenv("GEMINI_API_KEY", "AIza\u028bbad2")
_sanitize_loaded_credentials()
second = capsys.readouterr().err
assert "GEMINI_API_KEY" in first
assert second == "" # no repeat warning
def test_ascii_control_chars_not_stripped(self, monkeypatch, capsys):
"""ASCII control bytes (e.g. ESC 0x1B from terminal paste) are NOT non-ASCII.
This is intentional — they're valid ASCII for HTTP headers even if the
provider rejects them. Documents the scope of the sanitizer.
"""
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
_WARNED_KEYS.clear()
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant\x1bapi-key")
_sanitize_loaded_credentials()
assert os.environ["ANTHROPIC_API_KEY"] == "sk-ant\x1bapi-key"
assert capsys.readouterr().err == ""