fix(agent): dotenv-compatible inline-comment stripping in load_env_file

Unquoted values truncate only at a # preceded by whitespace (so
KEY=foo#bar stays intact); quoted values scan escape-aware for the
matching close quote, keep through it, and drop a trailing '# ...'
remainder. Verified empirically against python-dotenv 1.2.2 on a
10-case corpus (full parity). Supersedes the approach in #57718, whose
scanner corrupted foo#bar-style values.
This commit is contained in:
Teknium
2026-08-02 00:51:09 -07:00
parent 55abf206bf
commit 3fb066c372
2 changed files with 85 additions and 2 deletions

View File

@@ -23,6 +23,7 @@ Design rationale lives in ``docs/design/multiplexing-gateway.md`` (Workstream A)
from __future__ import annotations
import os
import re
from contextvars import ContextVar, Token
from pathlib import Path
from typing import Dict, Mapping, Optional
@@ -177,12 +178,50 @@ def get_secret(name: str, default: Optional[str] = None) -> Optional[str]:
return val if val is not None else default
def _strip_inline_comment(value: str) -> str:
"""Strip a dotenv-style inline comment from a raw ``.env`` value.
Mirrors python-dotenv (1.2.2) semantics, verified empirically:
- Quoted values: scan for the matching close quote
(backslash-escape-aware for double quotes, since ``save_env_value``
writes ``\\"``/``\\\\`` escapes). Everything through the close quote is
kept; a trailing ``# ...`` remainder after it is discarded, so
``KEY="has # inside" # trailing`` yields ``has # inside``. Non-comment
trailing junk leaves the value untouched (lenient, unlike dotenv's
hard parse error).
- Unquoted values: truncate only at a ``#`` PRECEDED BY WHITESPACE, so
``KEY=foo#bar`` keeps ``foo#bar`` while ``KEY=value # comment`` keeps
``value``. A value that *starts* with ``#`` (``KEY=#leading``) is kept.
"""
value = value.strip()
if not value:
return value
quote = value[0]
if quote in ("'", '"'):
i = 1
while i < len(value):
ch = value[i]
if quote == '"' and ch == "\\":
i += 2 # skip the escaped character
continue
if ch == quote:
remainder = value[i + 1:].lstrip()
if remainder.startswith("#"):
return value[: i + 1]
return value
i += 1
return value # unterminated quote: leave as-is
return re.split(r"\s+#", value, maxsplit=1)[0].strip()
def load_env_file(env_path: Path) -> Dict[str, str]:
"""Parse a ``.env`` file into a plain dict WITHOUT touching ``os.environ``.
Used to load a profile's secrets into an isolated mapping for
``set_secret_scope``. Parses the small KEY=VALUE subset Hermes writes
itself (``export`` prefix, ``#`` comments, matching quotes with the
itself (``export`` prefix, ``#`` comments — full-line and
dotenv-compatible inline, matching quotes with the
writer's ``\\"``/``\\\\`` escapes reversed — the same semantics as
``hermes_cli.config._parse_env_value``) but never mutates the process
environment — that isolation is the whole point.
@@ -217,7 +256,7 @@ def load_env_file(env_path: Path) -> Dict[str, str]:
key = key.strip()
if not key:
continue
secrets[key] = _parse_env_value(value)
secrets[key] = _parse_env_value(_strip_inline_comment(value))
return secrets

View File

@@ -137,6 +137,50 @@ class TestEnvFileParsing:
"EMPTY": "",
}
def test_inline_comment_stripped_from_unquoted_value(self, tmp_path):
"""`KEY=value # comment` → `value` (python-dotenv semantics)."""
(tmp_path / ".env").write_text("KEY=value # comment\nTABBED=foo\t#tabbed\n")
assert ss.load_env_file(tmp_path / ".env") == {
"KEY": "value",
"TABBED": "foo",
}
def test_hash_without_preceding_whitespace_is_not_a_comment(self, tmp_path):
"""`KEY=foo#bar` stays intact — dotenv only strips `#` after whitespace."""
(tmp_path / ".env").write_text("KEY=foo#bar\nLEAD=#leading\n")
assert ss.load_env_file(tmp_path / ".env") == {
"KEY": "foo#bar",
"LEAD": "#leading",
}
def test_inline_comment_after_quoted_value(self, tmp_path):
"""Quotes strip AND the trailing comment drops; inner `#` survives."""
(tmp_path / ".env").write_text(
"DQ=\"has # inside\" # trailing\n"
"SQ='single # inside' # trailing\n"
)
assert ss.load_env_file(tmp_path / ".env") == {
"DQ": "has # inside",
"SQ": "single # inside",
}
def test_inline_comment_with_escaped_quote_inside_value(self, tmp_path):
r"""Escape-aware close-quote scan: `\"` must not terminate the value."""
(tmp_path / ".env").write_text(
'KEY="a \\" quote # x" # trail\n'
)
assert ss.load_env_file(tmp_path / ".env") == {"KEY": 'a " quote # x'}
def test_round_trip_writer_value_with_trailing_comment(self, tmp_path):
"""A value quoted by the save_env_value writer survives an appended
inline comment byte-exactly."""
from hermes_cli.config import _quote_env_value
original = 'we#ird "tok\\en" # not a comment'
quoted = _quote_env_value(original)
(tmp_path / ".env").write_text(f"MY_TOKEN={quoted} # rotated 2026-08\n")
assert ss.load_env_file(tmp_path / ".env") == {"MY_TOKEN": original}