From d7069c2754745f8d7d9cc6d65bda756c845900fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E7=8C=AB?= Date: Fri, 13 Mar 2026 05:09:17 +0900 Subject: [PATCH 1/4] tmp: add to default branch to run --- .github/workflows/crowdin-bootstrap.yml | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/crowdin-bootstrap.yml diff --git a/.github/workflows/crowdin-bootstrap.yml b/.github/workflows/crowdin-bootstrap.yml new file mode 100644 index 00000000..99e6895e --- /dev/null +++ b/.github/workflows/crowdin-bootstrap.yml @@ -0,0 +1,64 @@ +name: Crowdin Bootstrap Target Translations + +on: + workflow_dispatch: + inputs: + base_branch: + description: "Repository branch whose committed target translations should be bootstrapped into Crowdin." + required: true + type: choice + options: + - main + - r-dev + confirm_bootstrap: + description: "Explicit confirmation for the one-time or exceptional bootstrap upload." + required: true + type: choice + options: + - "no" + - "yes-bootstrap-current-target-translations" + +permissions: + contents: read + +jobs: + bootstrap-target-translations: + runs-on: ubuntu-24.04 + + steps: + - name: Require explicit bootstrap confirmation + if: inputs.confirm_bootstrap != 'yes-bootstrap-current-target-translations' + run: | + echo "This workflow uploads committed target translations into Crowdin." >&2 + echo "Re-run it with confirm_bootstrap=yes-bootstrap-current-target-translations when you really want the bootstrap path." >&2 + exit 1 + + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.base_branch }} + + - name: Bootstrap committed target translations into Crowdin + uses: crowdin/github-action@v2 + with: + config: crowdin.yml + skip_ref_checkout: true + upload_sources: true + upload_translations: true + download_translations: false + create_pull_request: false + env: + GITHUB_TOKEN: ${{ github.token }} + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + - name: Summarize bootstrap outcome + env: + BASE_BRANCH: ${{ inputs.base_branch }} + run: | + { + echo "## Crowdin bootstrap completed" + echo + echo "- Bootstrapped committed target-locale files from \`${BASE_BRANCH}\` into Crowdin." + echo "- This workflow is for one-time or exceptional seeding only." + echo "- The steady-state sync path remains \`crowdin-sync.yml\`, which uploads only \`zh-CN\` source assets on normal push-triggered runs and returns translations through \`l10n_${BASE_BRANCH}\` pull requests." + } >> "$GITHUB_STEP_SUMMARY" From 4a0fc0ef38ce61095c7a6b19f0d8aa1b7f1dde55 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Sat, 14 Mar 2026 22:34:15 +0800 Subject: [PATCH 2/4] fix(webui): harden static file and port checks --- src/webui/webui_server.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/webui/webui_server.py b/src/webui/webui_server.py index fca2cee1..7221168e 100644 --- a/src/webui/webui_server.py +++ b/src/webui/webui_server.py @@ -3,7 +3,8 @@ import asyncio import mimetypes from pathlib import Path -from fastapi import FastAPI +import socket +from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from uvicorn import Config, Server as UvicornServer @@ -12,6 +13,19 @@ from src.common.logger import get_logger logger = get_logger("webui_server") +def _resolve_safe_static_file_path(static_path: Path, full_path: str) -> Path | None: + static_root = static_path.resolve() + + try: + candidate_path = (static_root / full_path).resolve() + candidate_path.relative_to(static_root) + except (OSError, RuntimeError, ValueError): + logger.warning(f"🚫 检测到疑似路径穿越请求: {full_path}") + return None + + return candidate_path + + class WebUIServer: """独立的 WebUI 服务器""" @@ -108,8 +122,11 @@ class WebUIServer: return response # 检查是否是静态文件 - file_path = static_path / full_path - if file_path.is_file() and file_path.exists(): + file_path = _resolve_safe_static_file_path(static_path, full_path) + if file_path is None: + raise HTTPException(status_code=404, detail="Not Found") + + if file_path.exists() and file_path.is_file(): # 自动检测 MIME 类型 media_type = mimetypes.guess_type(str(file_path))[0] response = FileResponse(file_path, media_type=media_type) @@ -242,8 +259,6 @@ class WebUIServer: def _check_port_available(self) -> bool: """检查端口是否可用(支持 IPv4 和 IPv6)""" - import socket - # 判断使用 IPv4 还是 IPv6 if ':' in self.host: # IPv6 地址 @@ -257,6 +272,8 @@ class WebUIServer: try: with socket.socket(family, socket.SOCK_STREAM) as s: s.settimeout(1) + # 与 Uvicorn 一致:允许在 TIME_WAIT 状态下绑定,减少误报 + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 尝试绑定端口 s.bind((test_host, self.port)) return True From 0244e5b64eae7e9d55ee6872af897078e744ef68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E7=8C=AB?= Date: Sat, 14 Mar 2026 23:40:10 +0900 Subject: [PATCH 3/4] =?UTF-8?q?update:=20=E5=8D=87=E7=BA=A7=20quick-algo?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96=E8=87=B3=200.1.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 56c944e8..9143f2c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ pydantic>=2.11.7 pypinyin>=0.54.0 python-dotenv>=1.1.1 python-multipart>=0.0.20 -quick-algo>=0.1.3 +quick-algo>=0.1.4 rich>=14.0.0 ruff>=0.12.2 setuptools>=80.9.0 From fc2599e39c2af37467a32087e3fa3c0b020f65e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E7=8C=AB?= Date: Sat, 21 Mar 2026 13:09:30 +0900 Subject: [PATCH 4/4] =?UTF-8?q?update:=20=E5=8D=87=E7=BA=A7=20quick-algo?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96=E8=87=B3=200.1.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 85dd74e3..c3a6ffff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "pypinyin>=0.54.0", "python-dotenv>=1.1.1", "python-multipart>=0.0.20", - "quick-algo>=0.1.3", + "quick-algo>=0.1.4", "rich>=14.0.0", "ruff>=0.12.2", "setuptools>=80.9.0",