fix(backends,storage,core): setuid in exec wrapper, missing-column migration, runner config dir for mirror, tagged prompt sources
This commit is contained in:
@@ -13,8 +13,9 @@ Events on the wire are the Anthropic ``MessageStreamEvent`` family: one
|
||||
rebased across the API calls claude makes inside the turn.
|
||||
|
||||
Process isolation: claude is spawned through a small exec wrapper that
|
||||
drops every inherited environment variable outside a whitelist, and,
|
||||
when ``RunnerConfig.user`` is set, under that uid.
|
||||
drops every inherited environment variable outside a whitelist and, when
|
||||
``RunnerConfig.user`` is set, switches to that uid before exec (done in the
|
||||
wrapper rather than via ``subprocess(user=...)``, which uvloop rejects).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -204,7 +205,7 @@ class ClaudeSdkBackend:
|
||||
self._mcp_disallowed = _mcp_disallowed(agent, mcp_tool_names or {})
|
||||
self._sessions: dict[str, _Live] = {}
|
||||
self._reaper: asyncio.Task[None] | None = None
|
||||
self._uid = _resolve_uid(self._runner.user)
|
||||
self._uid, self._gid = _resolve_ids(self._runner.user)
|
||||
self._wrapper: Path | None = None
|
||||
|
||||
@property
|
||||
@@ -411,6 +412,7 @@ class ClaudeSdkBackend:
|
||||
env = dict(opt.env)
|
||||
if self._runner.home is not None:
|
||||
env["HOME"] = str(self._runner.home)
|
||||
env.setdefault("CLAUDE_CONFIG_DIR", str(self._runner.home / ".claude"))
|
||||
plugins = self._plugins()
|
||||
system_prompt = (
|
||||
prompt_assembly.assemble(agent.prompt_sources)
|
||||
@@ -430,7 +432,6 @@ class ClaudeSdkBackend:
|
||||
cwd=str(agent.cwd),
|
||||
add_dirs=list(opt.add_dirs),
|
||||
env=env,
|
||||
user=self._runner.user,
|
||||
cli_path=str(self._exec_wrapper(extra_keep=tuple(env))),
|
||||
include_partial_messages=opt.include_partial_messages,
|
||||
session_store=self._store,
|
||||
@@ -472,6 +473,8 @@ class ClaudeSdkBackend:
|
||||
target=json.dumps(target),
|
||||
keep=json.dumps(keep),
|
||||
prefixes=json.dumps(list(ENV_KEEP_PREFIXES)),
|
||||
uid=json.dumps(self._uid),
|
||||
gid=json.dumps(self._gid),
|
||||
)
|
||||
digest = hashlib.sha256(script.encode("utf-8")).hexdigest()[:12]
|
||||
path = self._work_dir / f"claude-exec-{digest}.py"
|
||||
@@ -525,9 +528,15 @@ import sys
|
||||
TARGET = {target}
|
||||
KEEP = set({keep})
|
||||
PREFIXES = tuple({prefixes})
|
||||
UID = {uid}
|
||||
GID = {gid}
|
||||
env = {{
|
||||
k: v for k, v in os.environ.items() if k in KEEP or k.startswith(PREFIXES)
|
||||
}}
|
||||
if UID is not None and os.getuid() != UID:
|
||||
os.setgroups([])
|
||||
os.setgid(GID)
|
||||
os.setuid(UID)
|
||||
os.execve(TARGET, [TARGET, *sys.argv[1:]], env)
|
||||
"""
|
||||
|
||||
@@ -550,12 +559,11 @@ def _claude_binary() -> str:
|
||||
return found
|
||||
|
||||
|
||||
def _resolve_uid(user: str | None) -> int | None:
|
||||
def _resolve_ids(user: str | None) -> tuple[int | None, int | None]:
|
||||
if user is None:
|
||||
return None
|
||||
if user.isdigit():
|
||||
return int(user)
|
||||
return pwd.getpwnam(user).pw_uid
|
||||
return None, None
|
||||
record = pwd.getpwuid(int(user)) if user.isdigit() else pwd.getpwnam(user)
|
||||
return record.pw_uid, record.pw_gid
|
||||
|
||||
|
||||
def _chown_tree(root: Path, uid: int) -> None:
|
||||
|
||||
Reference in New Issue
Block a user