env sanitizer corrupts keys sharing suffix substrings

NousResearch/hermes-agent · cluster 17138

Data as of

Nothing open on GitHub. Fix merged in #17288. Check it covers your case.

The short answer

0open issues
0open PRs
6items grouped
Known?
Yes. 6 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #17288. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #17288. That is separate from the fix event.
Still open
Nothing open on GitHub.

A bug where the .env sanitizer incorrectly splits keys like GLM_API_KEY because they contain a known suffix (LM_API_KEY) as a substring, along with multiple proposed fixes for the substring matching logic.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #17288 fix(cli): prevent .env sanitizer from splitting GLM_API_KEY by LM_API_KEY suffix

    merged to main

    files: hermes_cli/config.py, tests/hermes_cli/test_config.py

    Show the diff
    • hermes_cli/config.py
      @@ -3710,18 +3710,27 @@ def _sanitize_env_lines(lines: list) -> list:
       
               # Detect concatenated KEY=VALUE pairs on one line.
               # Search for known KEY= patterns at any position in the line.
      -        split_positions = []
      +        # We collect full needle ranges so we can drop matches that are
      +        # fully contained within a longer overlapping needle. Without this,
      +        # suffix collisions corrupt the file: e.g. LM_API_KEY= inside
      +        # GLM_API_KEY= would otherwise split the line into "G\nLM_API_KEY=...".
      +        match_ranges: list[tuple[int, int]] = []
               for key_name in known_keys:
                   needle = key_name + "="
                   idx = stripped.find(needle)
                   while idx >= 0:
      -                split_positions.append(idx)
      +                match_ranges.append((idx, idx + len(needle)))
                       idx = stripped.find(needle, idx + len(needle))
       
      +        split_positions = sorted({
      +            s for s, e in match_ranges
      +            if not any(
      +                s2 <= s and e2 >= e and (s2, e2) != (s, e)
      +                for s2, e2 in match_ranges
      +            )
      +        })
      +
               if len(split_positions) > 1:
      -            split_positions.sort()
      -            # Deduplicate (shouldn't happen, but be safe)
      -            split_positions = sorted(set(split_positions))
                   for i, pos in enumerate(split_positions):
                       end = split_positions[i + 1] if i + 1 < len(split_positions) else len(stripped)
                       part = stripped[pos:end].strip()
    • tests/hermes_cli/test_config.py
      @@ -319,6 +319,23 @@ def test_value_ending_with_digits_still_splits(self):
               assert result[0].startswith("OPENROUTER_API_KEY=")
               assert result[1].startswith("OPENAI_BASE_URL=")
       
      +    def test_glm_suffix_collision_not_split(self):
      +        """GLM_API_KEY / GLM_BASE_URL must not be mangled by LM_API_KEY / LM_BASE_URL suffixes (#17138)."""
      +        lines = [
      +            "GLM_API_KEY=[redacted:secret-assignment]",
      +            "GLM_BASE_URL=https://api.z.ai/api/paas/v4\n",
      +        ]
      +        result = _sanitize_env_lines(lines)
      +        assert result == lines, f"GLM_* lines were corrupted by suffix collision: {result}"
      +
      +    def test_suffix_collision_does_not_break_real_concatenation(self):
      +        """A genuine concatenation that happens to start with a suffix-superset key still splits."""
      +        lines = ["GLM_API_KEY=[redacted:secret-assignment]"]
      +        result = _sanitize_env_lines(lines)
      +        assert len(result) == 2
      +        assert result[0].startswith("GLM_API_KEY=")
      +        assert result[1].startswith("LM_API_KEY=")
      +
           def test_save_env_value_fixes_corruption_on_write(self, tmp_path):
               """save_env_value sanitizes corrupted lines when writing a new key."""
               env_file = tmp_path / ".env"

4 PRs closed without merging

From the close comments: done elsewhere 3 · merged via another change 1

  • #17141 fix(cli): prevent .env sanitizer from splitting GLM_API_KEY by LM_API_KEY suffix · merged via another change → #17288
  • #17241 fix(config): sanitize_env substring false positive breaks valid API keys · done elsewhere
  • #17273 fix(config): prevent .env sanitizer from corrupting GLM_API_KEY and other suffix-trap keys · done elsewhere
  • #17291 fix(config): prevent _sanitize_env_lines from splitting valid keys via substring match · done elsewhere → 88e07c4, #17288, #17141, #17138
Closed issues · 1
  • Closed#17138[Bug]: On start, Hermes Agent is santizing GLM_API_KEY/GLM_BASE_URL into G\nLM_API_KEY and G\nLM_BASE_URL
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.33: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#17288
Closed issues
1
Attempts
4 PRs closed without merging
Fix link checked
29 Apr 2026

For your agent

The same facts, typed.