Skip to content

Commit 36f9412

Browse files
yasinBursaliclaude
authored andcommitted
fix(env-update): accept non-schema keys and preserve extras on round-trip
Two fixes for the .env write path: 1. Host agent: change strict reject (400) to warn-and-accept for keys not in .env.schema.json. Extension install hooks and GPU pinning write keys that are absent from the core schema (e.g. JWT_SECRET from LibreChat, COMFYUI_GPU_UUID from the installer). Rejecting them made the dashboard Settings save unusable after any extension install. 2. Dashboard API: _render_env_from_values no longer drops extras with empty values. The filter `value != ""` silently discarded keys like LLAMA_ARG_TENSOR_SPLIT="" on round-trip, even though empty values are semantically meaningful (disabling a setting). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 86c39fd commit 36f9412

File tree

4 files changed

+42
-8
lines changed

4 files changed

+42
-8
lines changed

dream-server/bin/dream-host-agent.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,10 +676,17 @@ def _handle_env_update(self):
676676
return
677677
key, _, value = stripped.partition("=")
678678
key = key.strip()
679-
if key not in allowed_keys:
680-
logger.warning("env_update rejected: unknown key %r from %s", key, client_ip)
681-
json_response(self, 400, {"error": f"Unknown key: {key}"})
679+
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', key):
680+
logger.warning("env_update rejected: invalid key name %r from %s", key[:40], client_ip)
681+
json_response(self, 400, {"error": f"Invalid key name: {key[:40]}"})
682682
return
683+
if key not in allowed_keys:
684+
# Warn but accept — extension install hooks and GPU pinning write
685+
# keys that are not in the core schema (e.g. JWT_SECRET from
686+
# LibreChat, COMFYUI_GPU_UUID from the installer). Rejecting
687+
# them breaks the dashboard Settings save for any install that
688+
# has ever enabled an extension.
689+
logger.info("env_update: non-schema key %r from %s (accepted)", key, client_ip)
683690
# Defense in depth: reject values containing control chars (null bytes,
684691
# escape sequences, etc.). splitlines() already consumed \n/\r/\u2028/\u2029;
685692
# this catches the residual edge cases flagged by security review.

dream-server/extensions/services/dashboard-api/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ def _render_env_from_values(values: dict[str, str]) -> str:
709709

710710
output_lines.append(line)
711711

712-
extras = [(key, value) for key, value in values.items() if key not in seen and value != ""]
712+
extras = [(key, value) for key, value in values.items() if key not in seen]
713713
if extras:
714714
if output_lines and output_lines[-1] != "":
715715
output_lines.append("")

dream-server/extensions/services/dashboard-api/tests/test_host_agent.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,14 +325,19 @@ def test_413_oversize_body(self, env_update_env):
325325
assert handler.response_code == 413
326326
assert "too large" in handler.parse_response()["error"].lower()
327327

328-
def test_400_unknown_key(self, env_update_env):
329-
body = _make_body("NOT_IN_SCHEMA=foo\n")
328+
def test_accepts_unknown_key_with_warning(self, env_update_env):
329+
"""Non-schema keys are accepted (warn, not reject) so extension-added
330+
keys (e.g. JWT_SECRET from LibreChat) don't break Settings save."""
331+
install_dir, data_dir = env_update_env
332+
(install_dir / ".env").write_text("DREAM_AGENT_KEY=old\n", encoding="utf-8")
333+
body = _make_body("DREAM_AGENT_KEY=old\nNOT_IN_SCHEMA=foo\n")
330334
handler = _FakeHandler(body)
331335

332336
_mod.AgentHandler._handle_env_update(handler)
333337

334-
assert handler.response_code == 400
335-
assert "Unknown key" in handler.parse_response()["error"]
338+
assert handler.response_code == 200
339+
env_text = (install_dir / ".env").read_text(encoding="utf-8")
340+
assert "NOT_IN_SCHEMA=foo" in env_text
336341

337342
def test_400_malformed_line(self, env_update_env):
338343
body = _make_body("THIS_LINE_HAS_NO_EQUALS\n")

dream-server/extensions/services/dashboard-api/tests/test_settings_env.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,25 @@ def test_api_settings_env_apply_rejects_disallowed_service(test_client):
287287

288288
assert response.status_code == 400
289289
assert "not eligible" in response.json()["detail"]["message"].lower()
290+
291+
292+
# --- Render round-trip fidelity ---
293+
294+
295+
def test_render_env_preserves_extras_with_empty_values():
296+
"""Keys with empty values must survive _render_env_from_values round-trip.
297+
298+
Regression guard for fork issue #335: the old filter
299+
``value != ""`` silently dropped keys like LLAMA_ARG_TENSOR_SPLIT=""
300+
on every save.
301+
"""
302+
from main import _render_env_from_values
303+
304+
values = {
305+
"LLM_BACKEND": "local",
306+
"TENSOR_SPLIT": "", # intentionally empty
307+
"GPU_UUID": "GPU-abc123",
308+
}
309+
rendered = _render_env_from_values(values)
310+
assert "TENSOR_SPLIT=" in rendered
311+
assert "GPU_UUID=GPU-abc123" in rendered

0 commit comments

Comments
 (0)