Skip to content

Commit 0af6f0e

Browse files
fix(dashboard-api): reject newlines and null bytes in env editor values
Adds validation in _serialize_form_values to reject values containing \n, \r, or \0. Prevents .env injection where a value like "3010\nINJECTED_KEY=malicious" could write an extra line to .env. Not exploitable in the current architecture (Docker Compose and Python dotenv treat values as literal strings), but closes a defense-in-depth gap identified during the #854 security audit. Adds 2 tests: newline injection rejected (400), null byte rejected (400). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 551598d commit 0af6f0e

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,12 @@ def _serialize_form_values(
520520

521521
for key, field in fields.items():
522522
value = raw_values.get(key, current_values.get(key, ""))
523+
# Reject newlines and null bytes to prevent .env injection
524+
if value is not None and any(c in str(value) for c in ("\n", "\r", "\0")):
525+
raise HTTPException(
526+
status_code=400,
527+
detail=f"Value for '{key}' contains invalid characters (newlines or null bytes are not allowed)",
528+
)
523529
if value is None:
524530
serialized[key] = current_values.get(key, "") if field.get("secret") else ""
525531
continue

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,35 @@ def test_api_settings_env_allows_existing_local_override(test_client, settings_e
185185
assert response.status_code == 200
186186
updated_env = env_path.read_text(encoding="utf-8")
187187
assert "LOCAL_OVERRIDE=updated" in updated_env
188+
189+
190+
def test_api_settings_env_rejects_newline_in_value(test_client, settings_env_fixture):
191+
response = test_client.put(
192+
"/api/settings/env",
193+
headers=test_client.auth_headers,
194+
json={
195+
"mode": "form",
196+
"values": {
197+
"LLM_BACKEND": "local\nINJECTED_KEY=malicious",
198+
},
199+
},
200+
)
201+
202+
assert response.status_code == 400
203+
assert "invalid characters" in response.json()["detail"]
204+
205+
206+
def test_api_settings_env_rejects_null_byte_in_value(test_client, settings_env_fixture):
207+
response = test_client.put(
208+
"/api/settings/env",
209+
headers=test_client.auth_headers,
210+
json={
211+
"mode": "form",
212+
"values": {
213+
"LLM_BACKEND": "local\x00injected",
214+
},
215+
},
216+
)
217+
218+
assert response.status_code == 400
219+
assert "invalid characters" in response.json()["detail"]

0 commit comments

Comments
 (0)