mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix: validate memory provider names before filesystem lookup and setup commands
Strict charset allowlist (alnum + - _, max 64) on the {name} path param of
the memory-provider config/setup endpoints. Prevents traversal-shaped names
from reaching find_provider_dir(), and setup now 404s when neither a
loadable provider nor a plugin manifest exists, so the command-running path
is only reachable for discoverable plugins. Adds regression tests.
This commit is contained in:
parent
4b184cbe54
commit
d9a4b5a5e5
2 changed files with 41 additions and 0 deletions
|
|
@ -4895,8 +4895,24 @@ def _write_memory_provider_config_values(
|
|||
save_env_value(env_key, secret)
|
||||
|
||||
|
||||
_MEMORY_PROVIDER_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$")
|
||||
|
||||
|
||||
def _require_valid_memory_provider_name(name: str) -> None:
|
||||
"""Reject provider names that could traverse outside the plugin dirs.
|
||||
|
||||
``name`` is interpolated into filesystem paths by ``find_provider_dir()``
|
||||
and gates which plugin manifest's setup commands run. A strict charset
|
||||
allowlist (no path separators, no dots) makes traversal impossible
|
||||
regardless of how the downstream lookup evolves.
|
||||
"""
|
||||
if not _MEMORY_PROVIDER_NAME_RE.fullmatch(name or ""):
|
||||
raise HTTPException(status_code=404, detail=f"Unknown memory provider: {name}")
|
||||
|
||||
|
||||
@app.get("/api/memory/providers/{name}/config")
|
||||
async def get_memory_provider_config(name: str):
|
||||
_require_valid_memory_provider_name(name)
|
||||
provider = _load_memory_provider(name)
|
||||
if provider is None:
|
||||
# Undeclared providers (e.g. builtin) have no config surface. Return an
|
||||
|
|
@ -4907,7 +4923,14 @@ async def get_memory_provider_config(name: str):
|
|||
|
||||
@app.post("/api/memory/providers/{name}/setup")
|
||||
async def setup_memory_provider(name: str, body: MemoryProviderSetupRequest):
|
||||
_require_valid_memory_provider_name(name)
|
||||
provider = _load_memory_provider(name)
|
||||
if provider is None and not _memory_provider_manifest(name):
|
||||
# No discoverable plugin directory → nothing whose manifest could
|
||||
# legitimately declare setup commands. Refuse before the
|
||||
# command-running path. (provider may be None with a manifest present
|
||||
# when its pip deps aren't installed yet — that's the setup use case.)
|
||||
raise HTTPException(status_code=404, detail=f"Unknown memory provider: {name}")
|
||||
if provider is not None and body.values:
|
||||
try:
|
||||
_write_memory_provider_config_values(name, provider, body.values)
|
||||
|
|
@ -4921,6 +4944,7 @@ async def setup_memory_provider(name: str, body: MemoryProviderSetupRequest):
|
|||
|
||||
@app.put("/api/memory/providers/{name}/config")
|
||||
async def update_memory_provider_config(name: str, body: MemoryProviderConfigUpdate):
|
||||
_require_valid_memory_provider_name(name)
|
||||
provider = _load_memory_provider(name)
|
||||
if provider is None:
|
||||
raise HTTPException(status_code=404, detail=f"Unknown memory provider: {name}")
|
||||
|
|
|
|||
|
|
@ -522,6 +522,23 @@ class TestWebServerEndpoints:
|
|||
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_memory_provider_endpoints_reject_traversal_names(self):
|
||||
# Names with path separators / dots must never reach the filesystem
|
||||
# lookup or the setup command path. 404 = rejected by the name guard;
|
||||
# 405 = the router collapsed the dotted path onto a different route
|
||||
# (equally safe — the handler never ran).
|
||||
for bad in ("..", "..%2f..%2fetc", "a.b", "x/y", ".hidden", ""):
|
||||
resp = self.client.get(f"/api/memory/providers/{bad}/config")
|
||||
assert resp.status_code in (404, 405), (bad, resp.status_code)
|
||||
resp = self.client.post(
|
||||
f"/api/memory/providers/{bad}/setup", json={"values": {}}
|
||||
)
|
||||
assert resp.status_code in (404, 405), (bad, resp.status_code)
|
||||
resp = self.client.put(
|
||||
f"/api/memory/providers/{bad}/config", json={"values": {}}
|
||||
)
|
||||
assert resp.status_code in (404, 405), (bad, resp.status_code)
|
||||
|
||||
def test_post_memory_provider_setup_persists_values_without_activation(self):
|
||||
from hermes_cli.config import load_config, load_env
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue