fix(tools): fall back to .hermes/.env when forwarded secret is empty#35583
Conversation
The docker_forward_env build loop only consulted the ~/.hermes/.env disk
fallback when a key was unset (value is None), not when it was present
but empty (""). A transient empty value in os.environ was therefore
forwarded into the sandbox container as `-e KEY=`, clobbering the correct
value on disk. Sandboxed workloads then read a zero-length secret and
failed auth (observed as intermittent Linear API 401s) with no gateway
restart and no .env rewrite.
Treat empty-string like unset (`if not value:` on the fallback) and never
forward a blank secret (`if value:` on the guard).
Fixes NousResearch#35580
|
Verified this fix is correct. The bug: The fix: Scope check: the change is in the single function that builds Docker
Edge case note: |
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved ✅
Changes
tools/environments/docker.py:if value is None:→if not value:(fallback) andif value is not None:→if value:(forward guard)tests/tools/test_docker_environment.py: 2 regression tests
Review
✅ Correctness
- Two-character change with clear reasoning: a transient empty string (
"") in the live env was treated as "present but empty" byvalue is None(which only catches unset keys), skipping the.hermes/.envfallback and forwarding `-e KEY=*** - Fix 1 (
if not value:on fallback): treatsNone,"", and falsy values the same — falls back to disk - Fix 2 (
if value:on forward guard): prevents `-e KEY=*** from ever being forwarded when the resolved value is empty/None - Both changes are conservative —
value == ""andvalue == Noneare the only cases where behavior changes, and both are correct
✅ Testing
test_init_env_args_uses_hermes_dotenv_for_empty_shell_env: live env has empty string, disk has the real value — asserts disk value wins and blank flag is not emittedtest_init_env_args_never_forwards_blank_secret: legitimately empty + no disk value — asserts the key is absent from args entirely- Both tests verified RED on old code, GREEN on fix (TDD style)
- Full file passes: 56 tests passed, 0 failed
✅ Code Quality
- Minimal change — 2 lines in production code, 44 lines of test code
- Detailed PR description with reproduction script and root cause analysis
- The bug report format is excellent (expected behavior, actual behavior, repro, environment)
Summary
Correct and minimal fix for an intermittent production issue. A transient empty string in the live env could override a valid .hermes/.env secret. Fix is conservative and well-tested.
Reviewed by Hermes Agent (cron job)
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved ✅
Review
A tight, well-documented fix for a subtle production bug where an empty-string env var would skip the .env fallback and be forwarded as -e KEY= to Docker containers, causing intermittent auth failures.
✅ Looks Good
- Minimal, correct fix:
if not value:instead ofif value is None:— 2 lines changed, maximum impact. - Both guards fixed: The empty-string detection and the forward guard both use truthiness, so blank values are never forwarded.
- Excellent PR description: Includes repro script, root cause analysis, and RED/GREEN test evidence.
- Good test coverage: Two regression tests with mocked env and disk values, covering both the empty-with-fallback and empty-without-fallback paths.
- No regression risk: The only behavior change is treating
""the same asNone, which is always the correct thing for secrets.
Checklist Summary
| Category | Status |
|---|---|
| Correctness | ✅ 2-line fix with precise semantics |
| Security | ✅ Prevents empty secrets from being forwarded |
| Code Quality | ✅ Clean conditionals, good commit message |
| Testing | ✅ 2 regression tests, RED/GREEN verified |
Reviewed by Hermes Agent (cron job)
…arch#36189) Adds me@simontaggart.com → SiTaggart to AUTHOR_MAP so the check-attribution gate passes for the docker_forward_env empty-secret fix (NousResearch#35583, fixes NousResearch#35580).
…ousResearch#35583) The docker_forward_env build loop only consulted the ~/.hermes/.env disk fallback when a key was unset (value is None), not when it was present but empty (""). A transient empty value in os.environ was therefore forwarded into the sandbox container as `-e KEY=`, clobbering the correct value on disk. Sandboxed workloads then read a zero-length secret and failed auth (observed as intermittent Linear API 401s) with no gateway restart and no .env rewrite. Treat empty-string like unset (`if not value:` on the fallback) and never forward a blank secret (`if value:` on the guard). Fixes NousResearch#35580
N.B. Hermes did all the invetigation and work, I'm no python dev
Expected behaviour: When a forwarded secret is momentarily empty (
"") in the gateway's live env, fall back to/.hermes/.env(as already happens when unset) and forward the correct value. A transient blank must never override a good on-disk secret, and an empty value must never be forwarded as-e KEY=.Actual behaviour: The disk fallback only triggers on
value is None, notvalue == "". An empty string skips the fallback and is forwarded as-e KEY=. Sandboxed workloads read a zero-length secret and fail auth while the correct value sits in/.hermes/.env. Observed as intermittent API 401s (a forwardedMY_SECRETarriving withlength: 0), with no gateway restart and no.envrewrite. Hit twice in production 9 days apart. It's invisible for gateway-side secrets (Slack/Telegram) because those never ride adocker exec -einto a container; only sandbox-consumed secrets surface it.Repro:
Fix: treat empty-string like unset (
if not value:on the fallback) and never forward a blank (if value:on the guard). Robust to whatever produces the transient blank (env mutation during concurrent spawns, partial reload). For strict back-compat, the minimal change is just the fallback line.for key in sorted(forward_keys): value = os.getenv(key) - if value is None: + if not value: value = hermes_env.get(key) - if value is not None: + if value: exec_env[key] = valueTests: two regression tests added to
tests/tools/test_docker_environment.py, mockingos.getenv(viamonkeypatch.setenv) and the hermes-env loader (_load_hermes_env_vars), no real files touched:test_init_env_args_uses_hermes_dotenv_for_empty_shell_env— live env hasMY_SECRET="", disk has the real value; asserts the container receives the disk value and no blank flag. Asserts on the resolved value in the args list, not the printed flag.test_init_env_args_never_forwards_blank_secret— legitimately empty + no disk value; asserts the key is not forwarded as-e MY_SECRET=at all.Verified RED on old code (both tests fail; old loop emits
['-e', 'MY_SECRET=']) and GREEN on the fix. Full file passes via the canonical runner:Environment: Observed in production on Ubuntu (AWS EC2 VPS), Python 3.11. Fix authored and tested on macOS 15.5 (arm64, Darwin 25.5.0), Python 3.11.9, Hermes Agent v0.15.1 (2026.5.29).
Fixes #35580