Compare commits

...

2 Commits

Author SHA1 Message Date
alt-glitch
9aed1b2fe7 fix: restore accidentally deleted websocket close code assertion 2026-04-25 10:06:46 +05:30
alt-glitch
f87dbdf0a8 fix(web): reject empty values in PUT /api/env
The endpoint accepted empty strings, allowing any .env key to be
silently blanked out from the web UI. Add Pydantic validators to
reject empty keys and values.
2026-04-25 09:59:11 +05:30
2 changed files with 46 additions and 1 deletions

View File

@@ -53,7 +53,7 @@ try:
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel from pydantic import BaseModel, field_validator
except ImportError: except ImportError:
raise SystemExit( raise SystemExit(
"Web UI requires fastapi and uvicorn.\n" "Web UI requires fastapi and uvicorn.\n"
@@ -425,6 +425,20 @@ class EnvVarUpdate(BaseModel):
key: str key: str
value: str value: str
@field_validator("key")
@classmethod
def key_must_be_nonempty(cls, v: str) -> str:
if not v.strip():
raise ValueError("key must not be empty")
return v
@field_validator("value")
@classmethod
def value_must_be_nonempty(cls, v: str) -> str:
if not v.strip():
raise ValueError("value must not be empty; use DELETE /api/env to remove a key")
return v
class EnvVarDelete(BaseModel): class EnvVarDelete(BaseModel):
key: str key: str

View File

@@ -1925,3 +1925,34 @@ class TestPtyWebSocket:
): ):
pass pass
assert exc.value.code == 4400 assert exc.value.code == 4400
class TestEnvVarUpdateValidation:
"""PUT /api/env must reject empty values to prevent .env key destruction."""
def test_rejects_empty_value(self):
from hermes_cli.web_server import EnvVarUpdate
import pydantic
with pytest.raises(pydantic.ValidationError):
EnvVarUpdate(key="SOME_KEY", value="")
def test_rejects_whitespace_only_value(self):
from hermes_cli.web_server import EnvVarUpdate
import pydantic
with pytest.raises(pydantic.ValidationError):
EnvVarUpdate(key="SOME_KEY", value=" ")
def test_accepts_nonempty_value(self):
from hermes_cli.web_server import EnvVarUpdate
update = EnvVarUpdate(key="SOME_KEY", value="sk-abc123")
assert update.value == "sk-abc123"
def test_rejects_empty_key(self):
from hermes_cli.web_server import EnvVarUpdate
import pydantic
with pytest.raises(pydantic.ValidationError):
EnvVarUpdate(key="", value="some-value")