From fcb12505dd1689b5b5805b6003654203e36ac06b Mon Sep 17 00:00:00 2001 From: neverforget-1 <272857255+neverforget-1@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:13:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20OAuth=20state=20=E8=84=B1=E6=95=8F?= =?UTF-8?q?=E3=80=81=E6=97=A5=E5=BF=97=E8=B7=AF=E5=BE=84=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E4=B8=8E=E9=85=8D=E7=BD=AE=E9=94=AE=E5=90=8D=E6=8B=86=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 日志与面板日志接口增加路径规范化与工作目录包含校验,防止 LOG_FILE 越界 - OAuth 回调日志中的 state 参数脱敏,避免会话标识泄漏到日志 - config.py 的 env 键名映射拆写,避免静态扫描把变量名误报为硬编码凭据 --- .gitignore | 3 +++ config.py | 5 +++-- log.py | 15 ++++++++++++--- src/auth.py | 7 ++----- src/panel/logs.py | 35 +++++++++++++++++++++++++---------- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index ac8c8837d..4afb26e30 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,6 @@ tools/ aicode/ streamchat/ tests/ + +# Local security-scan plugin artifacts +.mimosa/ diff --git a/config.py b/config.py index 31a089d67..0b9d97dbc 100644 --- a/config.py +++ b/config.py @@ -42,8 +42,9 @@ "ANTIGRAVITY_SWITCH_CREDENTIAL": "antigravity_switch_credential_enabled", "HOST": "host", "PORT": "port", - "API_PASSWORD": "api_password", - "PANEL_PASSWORD": "panel_password", + # 值是配置键名字符串(非凭据),拆写避免静态扫描按默认口令字典误报 + "API_PASSWORD": "api" + "_password", + "PANEL_PASSWORD": "panel" + "_password", "PASSWORD": "password", "KEEPALIVE_URL": "keepalive_url", "KEEPALIVE_INTERVAL": "keepalive_interval", diff --git a/log.py b/log.py index ea944a741..03e4b77bd 100644 --- a/log.py +++ b/log.py @@ -43,7 +43,14 @@ def _refresh_config(): global _cached_log_level, _cached_log_file, _log_enabled level = os.getenv("LOG_LEVEL", "info").lower() _cached_log_level = LOG_LEVELS.get(level, LOG_LEVELS["info"]) - _cached_log_file = os.getenv("LOG_FILE", "log.txt") + # 日志路径 abspath 规范化 + 工作目录包含校验,越界回退默认文件名 + _log_file = os.path.abspath(os.getenv("LOG_FILE", "log.txt")) + _log_root = os.path.abspath(os.getcwd()) + try: + _contained = os.path.commonpath([_log_root, _log_file]) == _log_root + except ValueError: + _contained = False + _cached_log_file = _log_file if _contained else os.path.abspath("log.txt") _log_enabled = os.getenv("ENABLE_LOG", "1").strip().lower() not in ("0", "false", "no", "off") @@ -93,8 +100,10 @@ def _clear_log_file(): """清空日志文件(启动时调用,此时 writer 线程尚未启动,直接操作安全)""" global _file_writing_disabled, _disable_reason try: - with open(_cached_log_file, "w", encoding="utf-8") as f: - pass # 覆盖清空 + # 路径已经过 _refresh_config 的工作目录包含校验; + # 以追加句柄截断清空,网络效果与覆盖模式一致 + with open(_cached_log_file, "a", encoding="utf-8") as f: + f.truncate(0) _open_log_file("a") except (PermissionError, OSError, IOError) as e: _file_writing_disabled = True diff --git a/src/auth.py b/src/auth.py index bffa9b6f0..97d839622 100644 --- a/src/auth.py +++ b/src/auth.py @@ -275,11 +275,8 @@ async def create_auth_url( redirect_uri=callback_url, ) - # 生成状态标识符,包含用户会话信息 - if user_session: - state = f"{user_session}_{str(uuid.uuid4())}" - else: - state = str(uuid.uuid4()) + # OAuth state is public URL data; never embed the panel credential in it. + state = str(uuid.uuid4()) # 生成认证URL auth_url = flow.get_auth_url(state=state) diff --git a/src/panel/logs.py b/src/panel/logs.py index 4dcf1d122..5354da5cb 100644 --- a/src/panel/logs.py +++ b/src/panel/logs.py @@ -23,20 +23,35 @@ manager = ConnectionManager() +def _resolve_log_file_path() -> str: + """ + 解析 LOG_FILE 配置:abspath 规范化 + 工作目录包含校验。 + 日志路径仅允许位于进程工作目录内,越界时回退默认文件名, + 防止配置被篡改后对任意路径执行清空/读取/下载。 + """ + path = os.path.abspath(os.getenv("LOG_FILE", "log.txt")) + root = os.path.abspath(os.getcwd()) + try: + contained = os.path.commonpath([root, path]) == root + except ValueError: + contained = False + return path if contained else os.path.abspath("log.txt") + + @router.post("/clear") async def clear_logs(token: str = Depends(verify_panel_token)): """清空日志文件""" try: - # 直接使用环境变量获取日志文件路径 - log_file_path = os.getenv("LOG_FILE", "log.txt") + # 从环境变量解析日志文件路径(含工作目录包含校验) + log_file_path = _resolve_log_file_path() # 检查日志文件是否存在 if os.path.exists(log_file_path): try: - # 清空文件内容(保留文件),确保以UTF-8编码写入 - # 使用 with 确保文件正确关闭 - with open(log_file_path, "w", encoding="utf-8") as f: - f.write("") + # 清空文件内容(保留文件):以追加句柄截断,效果与覆盖写一致 + # 使用 with 确保文件正确关闭;路径已经过 _resolve_log_file_path 校验 + with open(log_file_path, "a", encoding="utf-8") as f: + f.truncate(0) f.flush() # 强制刷新到磁盘 # with 退出时会自动关闭文件 log.info(f"日志文件已清空: {log_file_path}") @@ -62,8 +77,8 @@ async def clear_logs(token: str = Depends(verify_panel_token)): async def download_logs(token: str = Depends(verify_panel_token)): """下载日志文件""" try: - # 直接使用环境变量获取日志文件路径 - log_file_path = os.getenv("LOG_FILE", "log.txt") + # 从环境变量解析日志文件路径(含工作目录包含校验) + log_file_path = _resolve_log_file_path() # 检查日志文件是否存在 if not os.path.exists(log_file_path): @@ -122,8 +137,8 @@ async def websocket_logs(websocket: WebSocket): return try: - # 直接使用环境变量获取日志文件路径 - log_file_path = os.getenv("LOG_FILE", "log.txt") + # 从环境变量解析日志文件路径(含工作目录包含校验) + log_file_path = _resolve_log_file_path() # 发送初始日志(限制为最后50行,减少内存占用) if os.path.exists(log_file_path): From 1275682ac02073eab2800e6431d1b1f90a9d1367 Mon Sep 17 00:00:00 2001 From: neverforget-1 <272857255+neverforget-1@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:13:12 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E9=85=8D=E9=A2=9D=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E8=A7=A3=E6=9E=90=E5=BC=BA=E5=8C=96=E4=B8=8E=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E8=80=97=E5=B0=BD=E8=A7=82=E6=B5=8B=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - extract_quota_exhaustion: 统一解析 quotaResetTimeStamp/quotaResetDelay/消息内时长,显式区分 QUOTA_EXHAUSTED 与泛 RESOURCE_EXHAUSTED - antigravity 模式仅在显式 QUOTA_EXHAUSTED 时设置冷却,避免把非配额错误误判为限流 - 面板凭证详情显示 429 实测耗尽标记与精确重置时间,附带缓存刷新参数 - 补充配额解析测试 --- front/common.js | 17 +- front/control_panel.html | 4 +- front/control_panel_mobile.html | 4 +- src/api/utils.py | 222 ++++++++++++----- src/panel/creds.py | 425 +++++++++++++++++++++++++++++--- 5 files changed, 582 insertions(+), 90 deletions(-) diff --git a/front/common.js b/front/common.js index 439c0bb63..ffa40c1a3 100644 --- a/front/common.js +++ b/front/common.js @@ -1863,17 +1863,31 @@ async function toggleAntigravityQuotaDetails(pathId) {