fix(security): restrict local workspace writes to configured root
This commit is contained in:
@@ -31,6 +31,13 @@ class Settings(BaseSettings):
|
|||||||
cors_origins: str = ""
|
cors_origins: str = ""
|
||||||
base_url: str = ""
|
base_url: str = ""
|
||||||
|
|
||||||
|
# Optional: local directory where the backend is allowed to write "preserved" agent
|
||||||
|
# workspace files (e.g. USER.md/SELF.md/MEMORY.md). If empty, local writes are disabled
|
||||||
|
# and provisioning relies on the gateway API.
|
||||||
|
#
|
||||||
|
# Security note: do NOT point this at arbitrary system paths in production.
|
||||||
|
local_agent_workspace_root: str = ""
|
||||||
|
|
||||||
# Database lifecycle
|
# Database lifecycle
|
||||||
db_auto_migrate: bool = False
|
db_auto_migrate: bool = False
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -181,9 +182,22 @@ def _ensure_workspace_file(
|
|||||||
) -> None:
|
) -> None:
|
||||||
if not workspace_path or not name:
|
if not workspace_path or not name:
|
||||||
return
|
return
|
||||||
# `gateway.workspace_root` is sometimes configured as `~/.openclaw`.
|
# Only write to a dedicated, explicitly-configured local directory.
|
||||||
# Expand user here to avoid creating a literal `./~` directory under the backend cwd.
|
# Using `gateway.workspace_root` directly here is unsafe (and CodeQL correctly flags it)
|
||||||
root = Path(workspace_path).expanduser()
|
# because it is a DB-backed config value.
|
||||||
|
base_root = (settings.local_agent_workspace_root or "").strip()
|
||||||
|
if not base_root:
|
||||||
|
return
|
||||||
|
base = Path(base_root).expanduser()
|
||||||
|
|
||||||
|
# Derive a stable, safe directory name from the (potentially untrusted) workspace path.
|
||||||
|
# This prevents path traversal and avoids writing to arbitrary locations.
|
||||||
|
digest = hashlib.sha256(workspace_path.encode("utf-8")).hexdigest()[:16]
|
||||||
|
root = base / f"gateway-workspace-{digest}"
|
||||||
|
|
||||||
|
# Ensure `name` is a plain filename (no path separators).
|
||||||
|
if Path(name).name != name:
|
||||||
|
return
|
||||||
path = root / name
|
path = root / name
|
||||||
if not overwrite and path.exists():
|
if not overwrite and path.exists():
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user