From aa3f48e93c9eb2b406ba43c036a891f548fd80d3 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Sat, 30 May 2026 17:06:58 +0800 Subject: [PATCH] fix(web): confine /api/file to allowed dirs to prevent arbitrary file read --- channel/web/web_channel.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/channel/web/web_channel.py b/channel/web/web_channel.py index d230ec4c..96ad11a0 100644 --- a/channel/web/web_channel.py +++ b/channel/web/web_channel.py @@ -1315,12 +1315,19 @@ class FileServeHandler: file_path = params.path if not file_path or not os.path.isabs(file_path): raise web.notfound() - # Resolve symlinks and confine access to the configured root dir, + # Resolve symlinks and confine access to the allowed root dirs, # so this endpoint can't be abused to read arbitrary files (e.g. /etc/passwd, ~/.ssh). - # Defaults to the user home dir; set web_file_serve_root="/" to allow the whole filesystem. + # Defaults to the user home dir plus the agent workspace; set web_file_serve_root="/" + # to allow the whole filesystem. file_path = os.path.realpath(file_path) - serve_root = os.path.realpath(os.path.expanduser(conf().get("web_file_serve_root", "~") or "~")) - if serve_root != os.sep and os.path.commonpath([file_path, serve_root]) != serve_root: + serve_root = conf().get("web_file_serve_root", "~") or "~" + allowed_roots = [ + os.path.realpath(os.path.expanduser(serve_root)), + os.path.realpath(os.path.expanduser(conf().get("agent_workspace", "~/cow"))), + ] + if os.sep not in allowed_roots and not any( + os.path.commonpath([file_path, root]) == root for root in allowed_roots + ): raise web.notfound() if not os.path.isfile(file_path): raise web.notfound()