From 91db9e0422c0e2f48d9eb9c6a3263750c0b55b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 18:50:27 +0900 Subject: [PATCH 1/9] fix: harden runtime and supply chain security --- .github/workflows/build-docs.yml | 4 +- .github/workflows/dashboard_ci.yml | 2 +- .github/workflows/docker-image.yml | 20 +- .github/workflows/release.yml | 2 +- Dockerfile | 7 + astrbot/core/computer/booters/local.py | 3 +- astrbot/core/db/migration/sqlite_v3.py | 72 +- .../platform/sources/misskey/misskey_api.py | 9 +- .../platform/sources/satori/satori_adapter.py | 10 +- .../platform/sources/websocket_security.py | 56 ++ astrbot/core/utils/t2i/network_strategy.py | 1 - .../t2i/template/astrbot_powershell.html | 4 +- astrbot/core/utils/t2i/template/base.html | 4 +- dashboard/package.json | 20 +- dashboard/pnpm-lock.yaml | 864 ++++++++++++------ .../message_list_comps/IPythonToolBlock.vue | 14 +- .../src/components/shared/AstrBotConfigV4.vue | 3 +- dashboard/src/components/shared/Logo.vue | 27 +- .../theme/components/ArticleShare.vue | 32 +- docs/en/platform/aiocqhttp/napcat.md | 4 +- docs/scripts/upload_doc_images_to_r2.py | 2 +- docs/tests/test_upload_doc_images_to_r2.py | 59 ++ docs/zh/platform/aiocqhttp/napcat.md | 4 +- k8s/astrbot/02-deployment.yaml | 14 +- k8s/astrbot_with_napcat/02-deployment.yaml | 26 +- tests/unit/test_computer.py | 22 + tests/unit/test_sqlite_v3.py | 75 ++ tests/unit/test_t2i_security.py | 32 + tests/unit/test_websocket_security.py | 52 ++ 29 files changed, 1072 insertions(+), 372 deletions(-) create mode 100644 astrbot/core/platform/sources/websocket_security.py create mode 100644 docs/tests/test_upload_doc_images_to_r2.py create mode 100644 tests/unit/test_sqlite_v3.py create mode 100644 tests/unit/test_t2i_security.py create mode 100644 tests/unit/test_websocket_security.py diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 2464696803..3d28cef4db 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -23,7 +23,7 @@ jobs: run: npm run docs:build working-directory: './docs' - name: scp - uses: appleboy/scp-action@master + uses: appleboy/scp-action@7179e72a3fa4d4c33870a471708fda724fae7596 with: host: ${{ secrets.HOST_NEKO }} username: ${{ secrets.USERNAME }} @@ -31,7 +31,7 @@ jobs: source: 'docs/.vitepress/dist/*' target: '/tmp/' - name: script - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@8743aa11bfbda97acb45c151ae7a2e0b203f1914 with: host: ${{ secrets.HOST_NEKO }} username: ${{ secrets.USERNAME }} diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml index 46d2fea735..11dadc9425 100644 --- a/.github/workflows/dashboard_ci.yml +++ b/.github/workflows/dashboard_ci.yml @@ -45,7 +45,7 @@ jobs: - name: Create GitHub Release if: github.event_name == 'push' - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b with: tag: release-${{ github.sha }} owner: AstrBotDevs diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index d79d628c3f..8a8ae70cdf 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -64,20 +64,20 @@ jobs: echo "build_date=$build_date" >> $GITHUB_OUTPUT - name: Set QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a - name: Set Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd - name: Log in to DockerHub - uses: docker/login-action@v4 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Login to GitHub Container Registry if: env.HAS_GHCR_TOKEN == 'true' - uses: docker/login-action@v4 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ghcr.io username: ${{ env.GHCR_OWNER }} @@ -98,7 +98,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Build and Push Nightly Image - uses: docker/build-push-action@v7 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 with: context: . platforms: linux/amd64,linux/arm64 @@ -163,27 +163,27 @@ jobs: cp -r dashboard/dist data/ - name: Set QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a - name: Set Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd - name: Log in to DockerHub - uses: docker/login-action@v4 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Login to GitHub Container Registry if: env.HAS_GHCR_TOKEN == 'true' - uses: docker/login-action@v4 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ghcr.io username: ${{ env.GHCR_OWNER }} password: ${{ secrets.GHCR_GITHUB_TOKEN }} - name: Build and Push Release Image - uses: docker/build-push-action@v7 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 41f59f0a61..61ec840345 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10.28.2 diff --git a/Dockerfile b/Dockerfile index 992060d6ea..42e99e1eb7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,13 @@ RUN python -m pip install uv \ && uv pip install -r requirements.txt --no-cache-dir --system \ && uv pip install socksio uv pilk --no-cache-dir --system +RUN groupadd --system --gid 1000 astrbot \ + && useradd --system --uid 1000 --gid astrbot --home-dir /AstrBot --shell /usr/sbin/nologin astrbot \ + && mkdir -p /AstrBot/data \ + && chown -R astrbot:astrbot /AstrBot + EXPOSE 6185 +USER astrbot + CMD ["python", "main.py"] diff --git a/astrbot/core/computer/booters/local.py b/astrbot/core/computer/booters/local.py index a80ef0da28..5dfb52c411 100644 --- a/astrbot/core/computer/booters/local.py +++ b/astrbot/core/computer/booters/local.py @@ -112,10 +112,11 @@ async def exec( def _run() -> dict[str, Any]: try: result = subprocess.run( - [os.environ.get("PYTHON", sys.executable), "-c", code], + [sys.executable, "-c", code], timeout=timeout, capture_output=True, text=True, + shell=False, ) stdout = "" if silent else result.stdout stderr = result.stderr if result.returncode != 0 else "" diff --git a/astrbot/core/db/migration/sqlite_v3.py b/astrbot/core/db/migration/sqlite_v3.py index b326ebb449..bbf65da7fa 100644 --- a/astrbot/core/db/migration/sqlite_v3.py +++ b/astrbot/core/db/migration/sqlite_v3.py @@ -164,7 +164,7 @@ def insert_llm_metrics(self, metrics: dict) -> None: def get_base_stats(self, offset_sec: int = 86400) -> Stats: """获取 offset_sec 秒前到现在的基础统计数据""" - where_clause = f" WHERE timestamp >= {int(time.time()) - offset_sec}" + min_timestamp = int(time.time()) - offset_sec try: c = self.conn.cursor() @@ -174,8 +174,9 @@ def get_base_stats(self, offset_sec: int = 86400) -> Stats: c.execute( """ SELECT * FROM platform - """ - + where_clause, + WHERE timestamp >= :min_timestamp + """, + {"min_timestamp": min_timestamp}, ) platform = [] @@ -203,7 +204,7 @@ def get_total_message_count(self) -> int: def get_grouped_base_stats(self, offset_sec: int = 86400) -> Stats: """获取 offset_sec 秒前到现在的基础统计数据(合并)""" - where_clause = f" WHERE timestamp >= {int(time.time()) - offset_sec}" + min_timestamp = int(time.time()) - offset_sec try: c = self.conn.cursor() @@ -213,9 +214,10 @@ def get_grouped_base_stats(self, offset_sec: int = 86400) -> Stats: c.execute( """ SELECT name, SUM(count), timestamp FROM platform - """ - + where_clause - + " GROUP BY name", + WHERE timestamp >= :min_timestamp + GROUP BY name + """, + {"min_timestamp": min_timestamp}, ) platform = [] @@ -403,14 +405,15 @@ def get_filtered_conversations( try: # 构建查询条件 where_clauses = [] - params = [] + params: dict[str, Any] = {} # 平台筛选 if platforms and len(platforms) > 0: platform_conditions = [] - for platform in platforms: - platform_conditions.append("user_id LIKE ?") - params.append(f"{platform}:%") + for index, platform in enumerate(platforms): + param_name = f"platform_{index}" + platform_conditions.append(f"user_id LIKE :{param_name}") + params[param_name] = f"{platform}:%" if platform_conditions: where_clauses.append(f"({' OR '.join(platform_conditions)})") @@ -418,9 +421,10 @@ def get_filtered_conversations( # 消息类型筛选 if message_types and len(message_types) > 0: message_type_conditions = [] - for msg_type in message_types: - message_type_conditions.append("user_id LIKE ?") - params.append(f"%:{msg_type}:%") + for index, msg_type in enumerate(message_types): + param_name = f"message_type_{index}" + message_type_conditions.append(f"user_id LIKE :{param_name}") + params[param_name] = f"%:{msg_type}:%" if message_type_conditions: where_clauses.append(f"({' OR '.join(message_type_conditions)})") @@ -429,28 +433,32 @@ def get_filtered_conversations( if search_query: search_query = search_query.encode("unicode_escape").decode("utf-8") where_clauses.append( - "(title LIKE ? OR user_id LIKE ? OR cid LIKE ? OR history LIKE ?)", + "(" + "title LIKE :search_query OR user_id LIKE :search_query OR " + "cid LIKE :search_query OR history LIKE :search_query" + ")", ) - search_param = f"%{search_query}%" - params.extend([search_param, search_param, search_param, search_param]) + params["search_query"] = f"%{search_query}%" # 排除特定用户ID if exclude_ids and len(exclude_ids) > 0: - for exclude_id in exclude_ids: - where_clauses.append("user_id NOT LIKE ?") - params.append(f"{exclude_id}%") + for index, exclude_id in enumerate(exclude_ids): + param_name = f"exclude_id_{index}" + where_clauses.append(f"user_id NOT LIKE :{param_name}") + params[param_name] = f"{exclude_id}%" # 排除特定平台 if exclude_platforms and len(exclude_platforms) > 0: - for exclude_platform in exclude_platforms: - where_clauses.append("user_id NOT LIKE ?") - params.append(f"{exclude_platform}:%") + for index, exclude_platform in enumerate(exclude_platforms): + param_name = f"exclude_platform_{index}" + where_clauses.append(f"user_id NOT LIKE :{param_name}") + params[param_name] = f"{exclude_platform}:%" # 构建完整的 WHERE 子句 where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else "" # 构建计数查询 - count_sql = f"SELECT COUNT(*) FROM webchat_conversation{where_sql}" + count_sql = "SELECT COUNT(*) FROM webchat_conversation" + where_sql # 获取总记录数 c.execute(count_sql, params) @@ -460,14 +468,14 @@ def get_filtered_conversations( offset = (page - 1) * page_size # 构建分页数据查询 - data_sql = f""" - SELECT user_id, cid, created_at, updated_at, title, persona_id - FROM webchat_conversation - {where_sql} - ORDER BY updated_at DESC - LIMIT ? OFFSET ? - """ - query_params = params + [page_size, offset] + data_sql = ( + "SELECT user_id, cid, created_at, updated_at, title, persona_id\n" + "FROM webchat_conversation" + f"{where_sql}\n" + "ORDER BY updated_at DESC\n" + "LIMIT :page_size OFFSET :offset" + ) + query_params = {**params, "page_size": page_size, "offset": offset} # 获取分页数据 c.execute(data_sql, query_params) diff --git a/astrbot/core/platform/sources/misskey/misskey_api.py b/astrbot/core/platform/sources/misskey/misskey_api.py index 3e5eb9a90e..6757377ea9 100644 --- a/astrbot/core/platform/sources/misskey/misskey_api.py +++ b/astrbot/core/platform/sources/misskey/misskey_api.py @@ -15,6 +15,7 @@ from astrbot.api import logger +from ..websocket_security import require_secure_transport_url, to_websocket_url from .misskey_utils import FileIDExtractor # Constants @@ -56,10 +57,12 @@ def __init__(self, instance_url: str, access_token: str) -> None: async def connect(self) -> bool: try: - ws_url = self.instance_url.replace("https://", "wss://").replace( - "http://", - "ws://", + require_secure_transport_url( + self.instance_url, + label="Misskey instance URL", + allowed_schemes={"http", "https", "ws", "wss"}, ) + ws_url = to_websocket_url(self.instance_url) ws_url += f"/streaming?i={self.access_token}" self.websocket = await websockets.connect( diff --git a/astrbot/core/platform/sources/satori/satori_adapter.py b/astrbot/core/platform/sources/satori/satori_adapter.py index 5c2f7a37f3..6458a53658 100644 --- a/astrbot/core/platform/sources/satori/satori_adapter.py +++ b/astrbot/core/platform/sources/satori/satori_adapter.py @@ -27,6 +27,8 @@ ) from astrbot.core.platform.astr_message_event import MessageSession +from ..websocket_security import require_secure_transport_url + @register_platform_adapter( "satori", "Satori 协议适配器", support_streaming_message=False @@ -137,9 +139,11 @@ async def connect_websocket(self) -> None: logger.info(f"Satori 适配器正在连接到 WebSocket: {self.endpoint}") logger.info(f"Satori 适配器 HTTP API 地址: {self.api_base_url}") - if not self.endpoint.startswith(("ws://", "wss://")): - logger.error(f"无效的WebSocket URL: {self.endpoint}") - raise ValueError(f"WebSocket URL必须以ws://或wss://开头: {self.endpoint}") + require_secure_transport_url( + self.endpoint, + label="Satori WebSocket URL", + allowed_schemes={"ws", "wss"}, + ) try: websocket = await connect( diff --git a/astrbot/core/platform/sources/websocket_security.py b/astrbot/core/platform/sources/websocket_security.py new file mode 100644 index 0000000000..df66389d24 --- /dev/null +++ b/astrbot/core/platform/sources/websocket_security.py @@ -0,0 +1,56 @@ +import ipaddress +from urllib.parse import SplitResult, urlsplit, urlunsplit + +_ALLOWED_INSECURE_SUFFIXES = (".local", ".internal") + + +def _is_local_or_private_host(hostname: str | None) -> bool: + if not hostname: + return False + + normalized = hostname.strip("[]").lower() + if normalized == "localhost" or "." not in normalized: + return True + if normalized.endswith(_ALLOWED_INSECURE_SUFFIXES): + return True + + try: + address = ipaddress.ip_address(normalized) + except ValueError: + return False + + return address.is_loopback or address.is_private or address.is_link_local + + +def require_secure_transport_url( + url: str, + *, + label: str, + allowed_schemes: set[str], +) -> SplitResult: + parsed = urlsplit(url) + if parsed.scheme not in allowed_schemes: + allowed = ", ".join(sorted(allowed_schemes)) + raise ValueError(f"{label} must use one of: {allowed}") + + if parsed.scheme in {"http", "ws"} and not _is_local_or_private_host( + parsed.hostname + ): + raise ValueError( + f"{label} must use wss:// or https:// for non-local endpoints: {url}", + ) + + return parsed + + +def to_websocket_url(url: str) -> str: + parsed = urlsplit(url.rstrip("/")) + scheme_map = { + "http": "ws", + "https": "wss", + "ws": "ws", + "wss": "wss", + } + return urlunsplit( + parsed._replace(scheme=scheme_map[parsed.scheme]), + ) diff --git a/astrbot/core/utils/t2i/network_strategy.py b/astrbot/core/utils/t2i/network_strategy.py index 53d9441fab..c6c54db17c 100644 --- a/astrbot/core/utils/t2i/network_strategy.py +++ b/astrbot/core/utils/t2i/network_strategy.py @@ -129,7 +129,6 @@ async def render( if not template_name: template_name = "base" tmpl_str = await self.get_template(name=template_name) - text = text.replace("`", "\\`") return await self.render_custom_template( tmpl_str, {"text": text, "version": f"v{VERSION}"}, diff --git a/astrbot/core/utils/t2i/template/astrbot_powershell.html b/astrbot/core/utils/t2i/template/astrbot_powershell.html index 9ed3e77a55..93237023b6 100644 --- a/astrbot/core/utils/t2i/template/astrbot_powershell.html +++ b/astrbot/core/utils/t2i/template/astrbot_powershell.html @@ -177,8 +177,8 @@ - \ No newline at end of file + diff --git a/astrbot/core/utils/t2i/template/base.html b/astrbot/core/utils/t2i/template/base.html index 257cff3ff4..b38619e2f9 100644 --- a/astrbot/core/utils/t2i/template/base.html +++ b/astrbot/core/utils/t2i/template/base.html @@ -18,7 +18,7 @@
@@ -244,4 +244,4 @@ } } - \ No newline at end of file + diff --git a/dashboard/package.json b/dashboard/package.json index 7b4a7f071a..56f1d87317 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -17,17 +17,17 @@ "@tiptap/starter-kit": "2.1.7", "@tiptap/vue-3": "2.1.7", "apexcharts": "3.42.0", - "axios": ">=1.6.2 <1.10.0 || >1.10.0 <2.0.0", + "axios": "1.13.5", "axios-mock-adapter": "^1.22.0", "chance": "1.1.11", "date-fns": "2.30.0", - "dompurify": "^3.3.1", + "dompurify": "^3.3.2", "event-source-polyfill": "^1.0.31", "highlight.js": "^11.11.1", "js-md5": "^0.8.3", "katex": "^0.16.27", - "lodash": "4.17.21", - "markdown-it": "^14.1.0", + "lodash": "4.17.23", + "markdown-it": "^14.1.1", "markstream-vue": "^0.0.6", "mermaid": "^11.12.2", "monaco-editor": "^0.52.2", @@ -38,7 +38,7 @@ "stream-markdown": "^0.0.13", "stream-monaco": "^0.0.17", "vee-validate": "4.11.3", - "vite-plugin-vuetify": "1.0.2", + "vite-plugin-vuetify": "2.1.3", "vue": "3.3.4", "vue-i18n": "^11.1.5", "vue-router": "4.2.4", @@ -54,7 +54,7 @@ "@types/dompurify": "^3.0.5", "@types/markdown-it": "^14.1.2", "@types/node": "^20.5.7", - "@vitejs/plugin-vue": "4.3.3", + "@vitejs/plugin-vue": "5.2.4", "@vue/eslint-config-prettier": "8.0.0", "@vue/eslint-config-typescript": "11.0.3", "@vue/tsconfig": "^0.4.0", @@ -64,9 +64,15 @@ "sass": "1.66.1", "sass-loader": "13.3.2", "typescript": "5.1.6", - "vite": "4.4.9", + "vite": "6.4.1", "vue-cli-plugin-vuetify": "2.5.8", "vue-tsc": "1.8.8", "vuetify-loader": "^2.0.0-alpha.9" + }, + "pnpm": { + "overrides": { + "immutable": "4.3.8", + "lodash-es": "4.17.23" + } } } diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index ea8636c615..af45aa6f09 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -4,6 +4,10 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + immutable: 4.3.8 + lodash-es: 4.17.23 + importers: .: @@ -21,11 +25,11 @@ importers: specifier: 3.42.0 version: 3.42.0 axios: - specifier: '>=1.6.2 <1.10.0 || >1.10.0 <2.0.0' - version: 1.13.4 + specifier: 1.13.5 + version: 1.13.5 axios-mock-adapter: specifier: ^1.22.0 - version: 1.22.0(axios@1.13.4) + version: 1.22.0(axios@1.13.5) chance: specifier: 1.1.11 version: 1.1.11 @@ -33,8 +37,8 @@ importers: specifier: 2.30.0 version: 2.30.0 dompurify: - specifier: ^3.3.1 - version: 3.3.1 + specifier: ^3.3.2 + version: 3.3.2 event-source-polyfill: specifier: ^1.0.31 version: 1.0.31 @@ -48,11 +52,11 @@ importers: specifier: ^0.16.27 version: 0.16.28 lodash: - specifier: 4.17.21 - version: 4.17.21 + specifier: 4.17.23 + version: 4.17.23 markdown-it: - specifier: ^14.1.0 - version: 14.1.0 + specifier: ^14.1.1 + version: 14.1.1 markstream-vue: specifier: ^0.0.6 version: 0.0.6(katex@0.16.28)(mermaid@11.12.2)(shiki@3.22.0)(stream-markdown@0.0.13(shiki@3.22.0))(stream-monaco@0.0.17(monaco-editor@0.52.2))(vue-i18n@11.2.8(vue@3.3.4))(vue@3.3.4) @@ -84,8 +88,8 @@ importers: specifier: 4.11.3 version: 4.11.3(vue@3.3.4) vite-plugin-vuetify: - specifier: 1.0.2 - version: 1.0.2(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) + specifier: 2.1.3 + version: 2.1.3(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) vue: specifier: 3.3.4 version: 3.3.4 @@ -103,7 +107,7 @@ importers: version: 0.1.4 vuetify: specifier: 3.7.11 - version: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + version: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) yup: specifier: 1.2.0 version: 1.2.0 @@ -127,8 +131,8 @@ importers: specifier: ^20.5.7 version: 20.19.32 '@vitejs/plugin-vue': - specifier: 4.3.3 - version: 4.3.3(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4) + specifier: 5.2.4 + version: 5.2.4(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4) '@vue/eslint-config-prettier': specifier: 8.0.0 version: 8.0.0(@types/eslint@9.6.1)(eslint@8.48.0)(prettier@3.0.2) @@ -157,8 +161,8 @@ importers: specifier: 5.1.6 version: 5.1.6 vite: - specifier: 4.4.9 - version: 4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) + specifier: 6.4.1 + version: 6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) vue-cli-plugin-vuetify: specifier: 2.5.8 version: 2.5.8(sass-loader@13.3.2(sass@1.66.1)(webpack@5.105.0))(vue@3.3.4)(vuetify-loader@2.0.0-alpha.9(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(vuetify@3.7.11)(webpack@5.105.0))(webpack@5.105.0) @@ -213,135 +217,159 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - '@esbuild/android-arm64@0.18.20': - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.18.20': - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.18.20': - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.18.20': - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.18.20': - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.18.20': - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.18.20': - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.18.20': - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.18.20': - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.18.20': - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.18.20': - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.18.20': - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.18.20': - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.18.20': - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.18.20': - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.18.20': - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.18.20': - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.18.20': - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.18.20': - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.18.20': - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.18.20': - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.18.20': - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -460,6 +488,144 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + '@rushstack/eslint-patch@1.3.3': resolution: {integrity: sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==} @@ -745,6 +911,9 @@ packages: '@types/node@20.19.32': resolution: {integrity: sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==} + '@types/node@20.19.37': + resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -815,11 +984,11 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-vue@4.3.3': - resolution: {integrity: sha512-ssxyhIAZqB0TrpUg6R0cBpCuMk9jTIlO1GNSKKQD6S8VjnXi6JXKfUXjSsxey9IwQiaRGsO1WnW9Rkl1L6AJVw==} - engines: {node: ^14.18.0 || >=16.0.0} + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^4.0.0 + vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 '@volar/language-core@1.10.10': @@ -915,6 +1084,12 @@ packages: vue: ^3.0.0 vuetify: ^3.0.0-beta.4 + '@vuetify/loader-shared@2.1.2': + resolution: {integrity: sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig==} + peerDependencies: + vue: ^3.0.0 + vuetify: '>=3' + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -985,6 +1160,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1006,8 +1186,8 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} alien-signals@2.0.8: resolution: {integrity: sha512-844G1VLkk0Pe2SJjY0J8vp8ADI73IM4KliNu2OGlYzWpO28NexEUvjHTcFjFX3VXoiUtwTbHxLNI9ImkcoBqzA==} @@ -1042,14 +1222,15 @@ packages: peerDependencies: axios: '>= 0.17.0' - axios@1.13.4: - resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} hasBin: true big.js@5.2.2: @@ -1091,8 +1272,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001769: - resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001778: + resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1384,22 +1565,23 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - dompurify@3.3.1: - resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.3.2: + resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==} + engines: {node: '>=20'} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.307: + resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} emojis-list@3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} - enhanced-resolve@5.19.0: - resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + enhanced-resolve@5.20.0: + resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -1429,9 +1611,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: @@ -1542,6 +1724,15 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1684,8 +1875,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + immutable@4.3.8: + resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -1818,17 +2009,14 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash-es@4.17.23: resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1862,8 +2050,8 @@ packages: resolution: {integrity: sha512-nZpRTJj4S6bN0I5wsNBtgzDKx+HYBBSsvKjGdYw7/tPdrzfo3gUTt3ZbeAjPGeZaC6a4LFi4JdhTVeLm3F6TIQ==} engines: {node: '>=18'} - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true marked@16.4.2: @@ -1978,8 +2166,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -2072,6 +2260,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pinia@2.1.6: resolution: {integrity: sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==} peerDependencies: @@ -2127,8 +2319,8 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - prosemirror-changeset@2.3.1: - resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} + prosemirror-changeset@2.4.0: + resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} prosemirror-collab@1.3.1: resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} @@ -2139,8 +2331,8 @@ packages: prosemirror-dropcursor@1.8.2: resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} - prosemirror-gapcursor@1.4.0: - resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} prosemirror-history@1.5.0: resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} @@ -2154,8 +2346,8 @@ packages: prosemirror-markdown@1.13.4: resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} - prosemirror-menu@1.2.5: - resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + prosemirror-menu@1.3.0: + resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==} prosemirror-model@1.25.4: resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} @@ -2204,9 +2396,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2252,9 +2441,9 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@3.29.5: - resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true rope-sequence@1.3.4: @@ -2269,9 +2458,6 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2316,9 +2502,6 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2435,8 +2618,8 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - terser-webpack-plugin@5.3.16: - resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==} + terser-webpack-plugin@5.4.0: + resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -2466,6 +2649,10 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} @@ -2568,41 +2755,53 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-plugin-vuetify@1.0.2: - resolution: {integrity: sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==} - engines: {node: '>=12'} + vite-plugin-vuetify@2.1.3: + resolution: {integrity: sha512-Q4SC/4TqbNvaZIFb9YsfBqkGlYHbJJJ6uU3CnRBZqLUF3s5eCMVZAaV4GkTbehIH/bhSj42lMXztOwc71u6rVw==} + engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^2.7.0 || ^3.0.0 || ^4.0.0 + vite: '>=5' vue: ^3.0.0 - vuetify: ^3.0.0-beta.4 + vuetify: '>=3' - vite@4.4.9: - resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} - engines: {node: ^14.18.0 || >=16.0.0} + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' - terser: ^5.4.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} @@ -2718,8 +2917,8 @@ packages: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack@5.105.0: @@ -2786,12 +2985,12 @@ snapshots: dependencies: '@chevrotain/gast': 11.0.3 '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 '@chevrotain/gast@11.0.3': dependencies: '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 '@chevrotain/regexp-to-ast@11.0.3': {} @@ -2799,70 +2998,82 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@esbuild/android-arm64@0.18.20': + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/android-arm@0.18.20': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/android-x64@0.18.20': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.18.20': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.18.20': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.18.20': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.18.20': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-arm64@0.18.20': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-arm@0.18.20': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-ia32@0.18.20': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-loong64@0.18.20': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.18.20': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.18.20': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-riscv64@0.18.20': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/linux-s390x@0.18.20': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/linux-x64@0.18.20': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.18.20': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.18.20': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.18.20': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.18.20': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-ia32@0.18.20': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-x64@0.18.20': + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': optional: true '@eslint-community/eslint-utils@4.9.1(eslint@8.48.0)': @@ -2985,6 +3196,81 @@ snapshots: '@remirror/core-constants@3.0.0': {} + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + '@rushstack/eslint-patch@1.3.3': {} '@shikijs/core@3.22.0': @@ -3121,16 +3407,16 @@ snapshots: '@tiptap/pm@2.27.2': dependencies: - prosemirror-changeset: 2.3.1 + prosemirror-changeset: 2.4.0 prosemirror-collab: 1.3.1 prosemirror-commands: 1.7.1 prosemirror-dropcursor: 1.8.2 - prosemirror-gapcursor: 1.4.0 + prosemirror-gapcursor: 1.4.1 prosemirror-history: 1.5.0 prosemirror-inputrules: 1.5.1 prosemirror-keymap: 1.2.3 prosemirror-markdown: 1.13.4 - prosemirror-menu: 1.2.5 + prosemirror-menu: 1.3.0 prosemirror-model: 1.25.4 prosemirror-schema-basic: 1.2.4 prosemirror-schema-list: 1.5.1 @@ -3293,7 +3579,7 @@ snapshots: '@types/dompurify@3.2.0': dependencies: - dompurify: 3.3.1 + dompurify: 3.3.2 '@types/eslint-scope@3.7.7': dependencies: @@ -3332,6 +3618,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@20.19.37': + dependencies: + undici-types: 6.21.0 + '@types/semver@7.7.1': {} '@types/trusted-types@2.0.7': @@ -3425,9 +3715,9 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@4.3.3(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)': + '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)': dependencies: - vite: 4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) + vite: 6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) vue: 3.3.4 '@volar/language-core@1.10.10': @@ -3573,7 +3863,13 @@ snapshots: find-cache-dir: 3.3.2 upath: 2.0.1 vue: 3.3.4 - vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) + + '@vuetify/loader-shared@2.1.2(vue@3.3.4)(vuetify@3.7.11)': + dependencies: + upath: 2.0.1 + vue: 3.3.4 + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) '@webassemblyjs/ast@1.14.1': dependencies: @@ -3657,9 +3953,9 @@ snapshots: '@yr/monotone-cubic-spline@1.0.3': {} - acorn-import-phases@1.0.4(acorn@8.15.0): + acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 acorn-jsx@5.3.2(acorn@8.15.0): dependencies: @@ -3667,17 +3963,19 @@ snapshots: acorn@8.15.0: {} - ajv-formats@2.1.1(ajv@8.17.1): + acorn@8.16.0: {} + + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 fast-deep-equal: 3.1.3 ajv@6.12.6: @@ -3687,7 +3985,7 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 fast-uri: 3.1.0 @@ -3723,13 +4021,13 @@ snapshots: asynckit@0.4.0: {} - axios-mock-adapter@1.22.0(axios@1.13.4): + axios-mock-adapter@1.22.0(axios@1.13.5): dependencies: - axios: 1.13.4 + axios: 1.13.5 fast-deep-equal: 3.1.3 is-buffer: 2.0.5 - axios@1.13.4: + axios@1.13.5: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 @@ -3739,7 +4037,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.10.0: {} big.js@5.2.2: {} @@ -3762,10 +4060,10 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 - electron-to-chromium: 1.5.286 - node-releases: 2.0.27 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001778 + electron-to-chromium: 1.5.307 + node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) buffer-from@1.1.2: {} @@ -3779,7 +4077,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001778: {} ccount@2.0.1: {} @@ -3806,7 +4104,7 @@ snapshots: '@chevrotain/regexp-to-ast': 11.0.3 '@chevrotain/types': 11.0.3 '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 chokidar@3.6.0: dependencies: @@ -4088,7 +4386,7 @@ snapshots: dependencies: esutils: 2.0.3 - dompurify@3.3.1: + dompurify@3.3.2: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -4098,11 +4396,11 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.307: {} emojis-list@3.0.0: {} - enhanced-resolve@5.19.0: + enhanced-resolve@5.20.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.0 @@ -4128,30 +4426,34 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild@0.18.20: + esbuild@0.25.12: optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -4286,6 +4588,10 @@ snapshots: dependencies: reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -4441,7 +4747,7 @@ snapshots: ignore@5.3.2: {} - immutable@4.3.7: {} + immutable@4.3.8: {} import-fresh@3.3.1: dependencies: @@ -4487,7 +4793,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.32 + '@types/node': 20.19.37 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -4556,13 +4862,11 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.21: {} - lodash-es@4.17.23: {} lodash.merge@4.6.2: {} - lodash@4.17.21: {} + lodash@4.17.23: {} magic-string@0.30.21: dependencies: @@ -4594,7 +4898,7 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - markdown-it@14.1.0: + markdown-it@14.1.1: dependencies: argparse: 2.0.1 entities: 4.5.0 @@ -4651,7 +4955,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.13 dayjs: 1.11.19 - dompurify: 3.3.1 + dompurify: 3.3.2 katex: 0.16.28 khroma: 2.1.0 lodash-es: 4.17.23 @@ -4718,7 +5022,7 @@ snapshots: neo-async@2.6.2: {} - node-releases@2.0.27: {} + node-releases@2.0.36: {} normalize-path@3.0.0: {} @@ -4799,6 +5103,8 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.3: {} + pinia@2.1.6(typescript@5.1.6)(vue@3.3.4): dependencies: '@vue/devtools-api': 6.6.4 @@ -4849,7 +5155,7 @@ snapshots: property-information@7.1.0: {} - prosemirror-changeset@2.3.1: + prosemirror-changeset@2.4.0: dependencies: prosemirror-transform: 1.11.0 @@ -4869,7 +5175,7 @@ snapshots: prosemirror-transform: 1.11.0 prosemirror-view: 1.41.6 - prosemirror-gapcursor@1.4.0: + prosemirror-gapcursor@1.4.1: dependencies: prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.4 @@ -4896,10 +5202,10 @@ snapshots: prosemirror-markdown@1.13.4: dependencies: '@types/markdown-it': 14.1.2 - markdown-it: 14.1.0 + markdown-it: 14.1.1 prosemirror-model: 1.25.4 - prosemirror-menu@1.2.5: + prosemirror-menu@1.3.0: dependencies: crelt: 1.0.6 prosemirror-commands: 1.7.1 @@ -4962,10 +5268,6 @@ snapshots: queue-microtask@1.2.3: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -5004,8 +5306,35 @@ snapshots: robust-predicates@3.0.2: {} - rollup@3.29.5: + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 rope-sequence@1.3.4: {} @@ -5023,8 +5352,6 @@ snapshots: rw@1.3.3: {} - safe-buffer@5.2.1: {} - safer-buffer@2.1.2: {} sass-loader@13.3.2(sass@1.66.1)(webpack@5.105.0): @@ -5037,7 +5364,7 @@ snapshots: sass@1.66.1: dependencies: chokidar: 3.6.0 - immutable: 4.3.7 + immutable: 4.3.8 source-map-js: 1.2.1 schema-utils@3.3.0: @@ -5049,18 +5376,14 @@ snapshots: schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) semver@6.3.1: {} semver@7.7.4: {} - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -5181,19 +5504,18 @@ snapshots: tapable@2.3.0: {} - terser-webpack-plugin@5.3.16(webpack@5.105.0): + terser-webpack-plugin@5.4.0(webpack@5.105.0): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - serialize-javascript: 6.0.2 terser: 5.46.0 webpack: 5.105.0 terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -5203,6 +5525,11 @@ snapshots: tinyexec@1.0.2: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tippy.js@6.3.7: dependencies: '@popperjs/core': 2.11.8 @@ -5297,22 +5624,25 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-vuetify@1.0.2(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11): + vite-plugin-vuetify@2.1.3(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11): dependencies: - '@vuetify/loader-shared': 1.7.1(vue@3.3.4)(vuetify@3.7.11) + '@vuetify/loader-shared': 2.1.2(vue@3.3.4)(vuetify@3.7.11) debug: 4.4.3 upath: 2.0.1 - vite: 4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) + vite: 6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) vue: 3.3.4 - vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) transitivePeerDependencies: - supports-color - vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0): + vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0): dependencies: - esbuild: 0.18.20 + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 - rollup: 3.29.5 + rollup: 4.59.0 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.32 fsevents: 2.3.3 @@ -5359,7 +5689,7 @@ snapshots: eslint-visitor-keys: 3.4.3 espree: 9.6.1 esquery: 1.7.0 - lodash: 4.17.21 + lodash: 4.17.23 semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -5416,17 +5746,17 @@ snapshots: null-loader: 4.0.1(webpack@5.105.0) querystring: 0.2.1 upath: 2.0.1 - vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) webpack: 5.105.0 transitivePeerDependencies: - vue - vuetify@3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4): + vuetify@3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4): dependencies: vue: 3.3.4 optionalDependencies: typescript: 5.1.6 - vite-plugin-vuetify: 1.0.2(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) + vite-plugin-vuetify: 2.1.3(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) w3c-keyname@2.2.8: {} @@ -5435,7 +5765,7 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - webpack-sources@3.3.3: {} + webpack-sources@3.3.4: {} webpack@5.105.0: dependencies: @@ -5445,11 +5775,11 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.15.0 - acorn-import-phases: 1.0.4(acorn@8.15.0) + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.19.0 + enhanced-resolve: 5.20.0 es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -5461,9 +5791,9 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(webpack@5.105.0) + terser-webpack-plugin: 5.4.0(webpack@5.105.0) watchpack: 2.5.1 - webpack-sources: 3.3.3 + webpack-sources: 3.3.4 transitivePeerDependencies: - '@swc/core' - esbuild diff --git a/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue b/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue index 5ff9469a0e..118f1bfbd7 100644 --- a/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue +++ b/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue @@ -25,6 +25,7 @@ import { ref, computed, onMounted } from 'vue'; import { useModuleI18n } from '@/i18n/composables'; import { createHighlighter } from 'shiki'; +import DOMPurify from 'dompurify'; const props = defineProps({ toolCall: { @@ -67,6 +68,13 @@ const code = computed(() => { const result = computed(() => props.toolCall.result); +const escapeHtml = (value) => value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + const formattedResult = computed(() => { if (!result.value) return ''; try { @@ -82,13 +90,13 @@ const highlightedCode = computed(() => { return ''; } try { - return shikiHighlighter.value.codeToHtml(code.value, { + return DOMPurify.sanitize(shikiHighlighter.value.codeToHtml(code.value, { lang: 'python', theme: props.isDark ? 'min-dark' : 'github-light' - }); + })); } catch (err) { console.error('Failed to highlight code:', err); - return `
${code.value}
`; + return DOMPurify.sanitize(`
${escapeHtml(code.value)}
`); } }); diff --git a/dashboard/src/components/shared/AstrBotConfigV4.vue b/dashboard/src/components/shared/AstrBotConfigV4.vue index 9c86c419a6..4530479219 100644 --- a/dashboard/src/components/shared/AstrBotConfigV4.vue +++ b/dashboard/src/components/shared/AstrBotConfigV4.vue @@ -1,5 +1,6 @@ \ No newline at end of file + diff --git a/docs/en/platform/aiocqhttp/napcat.md b/docs/en/platform/aiocqhttp/napcat.md index bc081721a7..49538e7218 100644 --- a/docs/en/platform/aiocqhttp/napcat.md +++ b/docs/en/platform/aiocqhttp/napcat.md @@ -117,11 +117,11 @@ Switch back to NapCat's management panel, click `Network Configuration->New->Web In the newly opened window: - Check `Enable`. -- Fill in `URL` with `ws://HostIP:Port/ws`. For example, `ws://localhost:6199/ws` or `ws://127.0.0.1:6199/ws`. +- For local deployment, fill in `URL` with `ws://HostIP:Port/ws`. For example, `ws://localhost:6199/ws` or `ws://127.0.0.1:6199/ws`. > [!IMPORTANT] > 1. If deploying with Docker and both AstrBot and NapCat containers are connected to the same network, use `ws://astrbot:6199/ws` (refer to the Docker script in this documentation). -> 2. Due to Docker network isolation, when not on the same network, please use the internal network IP address or public network IP address ***(unsafe)*** to connect, i.e., `ws://(internal/public IP):6199/ws`. +> 2. For cross-host or public network deployment, do not expose plain `ws://`. Put AstrBot behind an HTTPS/TLS reverse proxy and connect with `wss://your-domain/ws` instead. - Message Format: `Array` - Heartbeat Interval: `5000` diff --git a/docs/scripts/upload_doc_images_to_r2.py b/docs/scripts/upload_doc_images_to_r2.py index 7db614dc47..f23a1205bf 100755 --- a/docs/scripts/upload_doc_images_to_r2.py +++ b/docs/scripts/upload_doc_images_to_r2.py @@ -220,7 +220,7 @@ def run_rclone_upload( else: print(f"Uploading to: {target}") - subprocess.run(cmd, check=True) + subprocess.run(cmd, check=True, shell=False) finally: tmp_path.unlink(missing_ok=True) diff --git a/docs/tests/test_upload_doc_images_to_r2.py b/docs/tests/test_upload_doc_images_to_r2.py new file mode 100644 index 0000000000..3465d7238d --- /dev/null +++ b/docs/tests/test_upload_doc_images_to_r2.py @@ -0,0 +1,59 @@ +import sys +import unittest +from importlib.util import module_from_spec, spec_from_file_location +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + + +def load_upload_module(): + script_path = ( + Path(__file__).resolve().parents[1] / "scripts" / "upload_doc_images_to_r2.py" + ) + spec = spec_from_file_location("upload_doc_images_to_r2", script_path) + if spec is None or spec.loader is None: + raise ImportError(f"Unable to load module from {script_path}") + module = module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +class UploadDocImagesToR2Test(unittest.TestCase): + def test_run_rclone_upload_uses_argument_list_without_shell(self): + module = load_upload_module() + + with TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + + with ( + patch.object(module.shutil, "which", return_value="/usr/bin/rclone"), + patch.object(module.subprocess, "run") as mock_run, + ): + module.run_rclone_upload( + root, + "r2:docs-bucket/assets", + ["guide/image.png"], + dry_run=False, + ) + + args, kwargs = mock_run.call_args + files_from_path = args[0][5] + self.assertEqual( + args[0], + [ + "rclone", + "copy", + str(root), + "r2:docs-bucket/assets", + "--files-from", + files_from_path, + "--create-empty-src-dirs", + ], + ) + self.assertTrue(kwargs["check"]) + self.assertIs(kwargs.get("shell"), False) + + +if __name__ == "__main__": + unittest.main() diff --git a/docs/zh/platform/aiocqhttp/napcat.md b/docs/zh/platform/aiocqhttp/napcat.md index 240579a712..ade384d387 100644 --- a/docs/zh/platform/aiocqhttp/napcat.md +++ b/docs/zh/platform/aiocqhttp/napcat.md @@ -110,11 +110,11 @@ docker logs napcat 在新弹出的窗口中: - 勾选 `启用`。 -- `URL` 填写 `ws://宿主机IP:端口/ws`。如 `ws://localhost:6199/ws` 或 `ws://127.0.0.1:6199/ws`。 +- 本机部署时,`URL` 填写 `ws://宿主机IP:端口/ws`。如 `ws://localhost:6199/ws` 或 `ws://127.0.0.1:6199/ws`。 > [!IMPORTANT] > 1. 如果采用 Docker 部署并同时把 AstrBot 和 NapCat 两个容器接入了同一网络,`ws://astrbot:6199/ws`(参考本文档的 Docker 脚本)。 -> 2. 由于 Docker 网络隔离的原因,不在同一个网络时请使用内网 IP 地址或公网 IP 地址 ***(不安全)*** 进行连接,即 `ws://(内网/公网):6199/ws`。 +> 2. 如果是跨主机或公网部署,请不要暴露明文 `ws://`。应通过 HTTPS/TLS 反向代理暴露 AstrBot,并使用 `wss://你的域名/ws` 连接。 - 消息格式:`Array` - 心跳间隔: `5000` diff --git a/k8s/astrbot/02-deployment.yaml b/k8s/astrbot/02-deployment.yaml index d2799ab900..812f170477 100644 --- a/k8s/astrbot/02-deployment.yaml +++ b/k8s/astrbot/02-deployment.yaml @@ -17,10 +17,22 @@ spec: labels: app: astrbot-standalone spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault containers: - name: astrbot image: soulter/astrbot:latest imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL env: - name: TZ value: "Asia/Shanghai" @@ -46,4 +58,4 @@ spec: - name: localtime hostPath: path: /etc/localtime - type: File \ No newline at end of file + type: File diff --git a/k8s/astrbot_with_napcat/02-deployment.yaml b/k8s/astrbot_with_napcat/02-deployment.yaml index 8b41f4ffb0..7beeca056a 100644 --- a/k8s/astrbot_with_napcat/02-deployment.yaml +++ b/k8s/astrbot_with_napcat/02-deployment.yaml @@ -22,11 +22,23 @@ spec: subdomain: astrbot-stack # 优雅关闭时间,给 NapCat 足够时间保存状态 terminationGracePeriodSeconds: 60 + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault # 初始化容器:首次生成随机 machine-id,后续复用 initContainers: - name: init-machine-id - image: busybox:latest + image: busybox:1.37.0 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL command: - /bin/sh - -c @@ -47,6 +59,11 @@ spec: - name: napcat image: mlikiowa/napcat-docker:latest imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL env: - name: NAPCAT_UID value: "1000" @@ -86,6 +103,11 @@ spec: - name: astrbot image: soulter/astrbot:latest imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL env: - name: TZ value: "Asia/Shanghai" @@ -123,4 +145,4 @@ spec: - name: localtime hostPath: path: /etc/localtime - type: File \ No newline at end of file + type: File diff --git a/tests/unit/test_computer.py b/tests/unit/test_computer.py index 07a5449c19..36dfec48c4 100644 --- a/tests/unit/test_computer.py +++ b/tests/unit/test_computer.py @@ -243,6 +243,28 @@ async def test_exec_with_env(self): class TestLocalPythonComponent: """Tests for LocalPythonComponent.""" + @pytest.mark.asyncio + async def test_exec_uses_fixed_python_executable(self): + """Test Python execution ignores dynamic executable overrides.""" + python = LocalPythonComponent() + + with ( + patch.dict("os.environ", {"PYTHON": "malicious-python"}), + patch("astrbot.core.computer.booters.local.subprocess.run") as mock_run, + ): + mock_run.return_value = MagicMock( + returncode=0, + stdout="hello\n", + stderr="", + ) + + result = await python.exec("print('hello')") + + args, kwargs = mock_run.call_args + assert args[0] == [sys.executable, "-c", "print('hello')"] + assert kwargs.get("shell") is False + assert result["data"]["output"]["text"] == "hello\n" + @pytest.mark.asyncio async def test_exec_simple_code(self): """Test executing simple Python code.""" diff --git a/tests/unit/test_sqlite_v3.py b/tests/unit/test_sqlite_v3.py new file mode 100644 index 0000000000..7708d54057 --- /dev/null +++ b/tests/unit/test_sqlite_v3.py @@ -0,0 +1,75 @@ +from unittest.mock import MagicMock, patch + +from astrbot.core.db.migration.sqlite_v3 import SQLiteDatabase + + +class TestSQLiteV3QueryParameterization: + def _create_db_with_mock_cursor(self): + db = SQLiteDatabase(":memory:") + cursor = MagicMock() + cursor.fetchall.return_value = [] + cursor.fetchone.return_value = (0,) + + conn = MagicMock() + conn.cursor.return_value = cursor + db.conn = conn + return db, cursor + + def test_get_base_stats_uses_bound_parameter(self): + db, cursor = self._create_db_with_mock_cursor() + + with patch("astrbot.core.db.migration.sqlite_v3.time.time", return_value=1000): + db.get_base_stats(offset_sec=60) + + sql, params = cursor.execute.call_args.args + assert ( + " ".join(sql.split()) + == "SELECT * FROM platform WHERE timestamp >= :min_timestamp" + ) + assert params == {"min_timestamp": 940} + + def test_get_grouped_base_stats_uses_bound_parameter(self): + db, cursor = self._create_db_with_mock_cursor() + + with patch("astrbot.core.db.migration.sqlite_v3.time.time", return_value=1000): + db.get_grouped_base_stats(offset_sec=60) + + sql, params = cursor.execute.call_args.args + assert " ".join(sql.split()) == ( + "SELECT name, SUM(count), timestamp FROM platform " + "WHERE timestamp >= :min_timestamp GROUP BY name" + ) + assert params == {"min_timestamp": 940} + + def test_get_filtered_conversations_uses_named_parameters(self): + db, cursor = self._create_db_with_mock_cursor() + + db.get_filtered_conversations( + page=2, + page_size=10, + platforms=["qq"], + message_types=["group"], + search_query="x' OR 1=1 --", + exclude_ids=["admin"], + exclude_platforms=["slack"], + ) + + count_sql, count_params = cursor.execute.call_args_list[0].args + data_sql, data_params = cursor.execute.call_args_list[1].args + + assert ":platform_0" in count_sql + assert ":message_type_0" in count_sql + assert ":search_query" in count_sql + assert ":exclude_id_0" in count_sql + assert ":exclude_platform_0" in count_sql + assert "x' OR 1=1 --" not in count_sql + assert count_params["platform_0"] == "qq:%" + assert count_params["message_type_0"] == "%:group:%" + assert count_params["search_query"] == "%x' OR 1=1 --%" + assert count_params["exclude_id_0"] == "admin%" + assert count_params["exclude_platform_0"] == "slack:%" + + assert ":page_size" in data_sql + assert ":offset" in data_sql + assert data_params["page_size"] == 10 + assert data_params["offset"] == 10 diff --git a/tests/unit/test_t2i_security.py b/tests/unit/test_t2i_security.py new file mode 100644 index 0000000000..e66a89fda6 --- /dev/null +++ b/tests/unit/test_t2i_security.py @@ -0,0 +1,32 @@ +from pathlib import Path +from unittest.mock import AsyncMock + +import pytest + +from astrbot.core.utils.t2i.network_strategy import NetworkRenderStrategy + + +@pytest.mark.asyncio +async def test_network_strategy_render_preserves_backticks(): + strategy = NetworkRenderStrategy() + strategy.get_template = AsyncMock(return_value="template") + strategy.render_custom_template = AsyncMock(return_value="rendered") + + result = await strategy.render("```python\nprint('hi')\n```") + + assert result == "rendered" + _, tmpl_data, return_url = strategy.render_custom_template.call_args.args + assert tmpl_data["text"] == "```python\nprint('hi')\n```" + assert return_url is False + + +def test_t2i_templates_use_json_serialization_for_text(): + template_paths = [ + Path("astrbot/core/utils/t2i/template/base.html"), + Path("astrbot/core/utils/t2i/template/astrbot_powershell.html"), + ] + + for template_path in template_paths: + content = template_path.read_text(encoding="utf-8") + assert "text | safe" not in content + assert "text | tojson" in content diff --git a/tests/unit/test_websocket_security.py b/tests/unit/test_websocket_security.py new file mode 100644 index 0000000000..91d2e15b05 --- /dev/null +++ b/tests/unit/test_websocket_security.py @@ -0,0 +1,52 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from astrbot.core.platform.sources.misskey.misskey_api import StreamingClient +from astrbot.core.platform.sources.websocket_security import ( + require_secure_transport_url, + to_websocket_url, +) + + +def test_require_secure_transport_url_allows_local_ws() -> None: + parsed = require_secure_transport_url( + "ws://localhost:5140/satori/v1/events", + label="Satori WebSocket URL", + allowed_schemes={"ws", "wss"}, + ) + + assert parsed.scheme == "ws" + + +def test_require_secure_transport_url_rejects_public_ws() -> None: + with pytest.raises( + ValueError, + match="must use wss:// or https:// for non-local endpoints", + ): + require_secure_transport_url( + "ws://example.com/events", + label="Satori WebSocket URL", + allowed_schemes={"ws", "wss"}, + ) + + +def test_to_websocket_url_converts_https_to_wss() -> None: + assert to_websocket_url("https://example.com") == "wss://example.com" + assert ( + to_websocket_url("http://localhost:5140/satori/v1") + == "ws://localhost:5140/satori/v1" + ) + + +@pytest.mark.asyncio +async def test_streaming_client_rejects_remote_http_instance() -> None: + client = StreamingClient("http://example.com", "token") + + with patch( + "astrbot.core.platform.sources.misskey.misskey_api.websockets.connect", + new_callable=AsyncMock, + ) as mock_connect: + assert await client.connect() is False + + mock_connect.assert_not_awaited() From 4e8f132c2bcbe6fa7bcc9ee217145f283a6994de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 18:58:40 +0900 Subject: [PATCH 2/9] chore: use version tags for workflow actions --- .github/workflows/build-docs.yml | 6 +++--- .github/workflows/dashboard_ci.yml | 2 +- .github/workflows/docker-image.yml | 20 ++++++++++---------- .github/workflows/release.yml | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 3d28cef4db..f0c25a6c89 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest # 运行环境 steps: - name: checkout - uses: actions/checkout@master + uses: actions/checkout@v6 - name: nodejs installation uses: actions/setup-node@v6 with: @@ -23,7 +23,7 @@ jobs: run: npm run docs:build working-directory: './docs' - name: scp - uses: appleboy/scp-action@7179e72a3fa4d4c33870a471708fda724fae7596 + uses: appleboy/scp-action@v1.0.0 with: host: ${{ secrets.HOST_NEKO }} username: ${{ secrets.USERNAME }} @@ -31,7 +31,7 @@ jobs: source: 'docs/.vitepress/dist/*' target: '/tmp/' - name: script - uses: appleboy/ssh-action@8743aa11bfbda97acb45c151ae7a2e0b203f1914 + uses: appleboy/ssh-action@v1.2.5 with: host: ${{ secrets.HOST_NEKO }} username: ${{ secrets.USERNAME }} diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml index 11dadc9425..7bfbf63615 100644 --- a/.github/workflows/dashboard_ci.yml +++ b/.github/workflows/dashboard_ci.yml @@ -45,7 +45,7 @@ jobs: - name: Create GitHub Release if: github.event_name == 'push' - uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b + uses: ncipollo/release-action@v1.20.0 with: tag: release-${{ github.sha }} owner: AstrBotDevs diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8a8ae70cdf..ccf5604357 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -64,20 +64,20 @@ jobs: echo "build_date=$build_date" >> $GITHUB_OUTPUT - name: Set QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a + uses: docker/setup-qemu-action@v4.0.0 - name: Set Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd + uses: docker/setup-buildx-action@v4.0.0 - name: Log in to DockerHub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Login to GitHub Container Registry if: env.HAS_GHCR_TOKEN == 'true' - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ env.GHCR_OWNER }} @@ -98,7 +98,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Build and Push Nightly Image - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 + uses: docker/build-push-action@v7.0.0 with: context: . platforms: linux/amd64,linux/arm64 @@ -163,27 +163,27 @@ jobs: cp -r dashboard/dist data/ - name: Set QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a + uses: docker/setup-qemu-action@v4.0.0 - name: Set Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd + uses: docker/setup-buildx-action@v4.0.0 - name: Log in to DockerHub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Login to GitHub Container Registry if: env.HAS_GHCR_TOKEN == 'true' - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ env.GHCR_OWNER }} password: ${{ secrets.GHCR_GITHUB_TOKEN }} - name: Build and Push Release Image - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 + uses: docker/build-push-action@v7.0.0 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61ec840345..68da87dfd5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 + uses: pnpm/action-setup@v4.3.0 with: version: 10.28.2 From 1fd0decbb036ced0a2fe5a87d31b9cfe07882486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:02:22 +0900 Subject: [PATCH 3/9] fix: address security review feedback --- astrbot/core/platform/sources/websocket_security.py | 2 +- .../chat/message_list_comps/IPythonToolBlock.vue | 2 +- tests/unit/test_websocket_security.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/astrbot/core/platform/sources/websocket_security.py b/astrbot/core/platform/sources/websocket_security.py index df66389d24..8ca6fa41ba 100644 --- a/astrbot/core/platform/sources/websocket_security.py +++ b/astrbot/core/platform/sources/websocket_security.py @@ -9,7 +9,7 @@ def _is_local_or_private_host(hostname: str | None) -> bool: return False normalized = hostname.strip("[]").lower() - if normalized == "localhost" or "." not in normalized: + if normalized == "localhost": return True if normalized.endswith(_ALLOWED_INSECURE_SUFFIXES): return True diff --git a/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue b/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue index 118f1bfbd7..252812394d 100644 --- a/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue +++ b/dashboard/src/components/chat/message_list_comps/IPythonToolBlock.vue @@ -96,7 +96,7 @@ const highlightedCode = computed(() => { })); } catch (err) { console.error('Failed to highlight code:', err); - return DOMPurify.sanitize(`
${escapeHtml(code.value)}
`); + return `
${escapeHtml(code.value)}
`; } }); diff --git a/tests/unit/test_websocket_security.py b/tests/unit/test_websocket_security.py index 91d2e15b05..42a56b2316 100644 --- a/tests/unit/test_websocket_security.py +++ b/tests/unit/test_websocket_security.py @@ -31,6 +31,18 @@ def test_require_secure_transport_url_rejects_public_ws() -> None: ) +def test_require_secure_transport_url_rejects_bare_hostname_ws() -> None: + with pytest.raises( + ValueError, + match="must use wss:// or https:// for non-local endpoints", + ): + require_secure_transport_url( + "ws://prod/events", + label="Satori WebSocket URL", + allowed_schemes={"ws", "wss"}, + ) + + def test_to_websocket_url_converts_https_to_wss() -> None: assert to_websocket_url("https://example.com") == "wss://example.com" assert ( From ac497dd7ee2f1bb33b15986077d4f1be5a5b1d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:12:30 +0900 Subject: [PATCH 4/9] fix: address websocket review feedback --- Dockerfile | 1 + .../platform/sources/misskey/misskey_api.py | 9 ++---- .../platform/sources/websocket_security.py | 28 +++++++++++++++++-- k8s/astrbot/02-deployment.yaml | 1 + k8s/astrbot_with_napcat/02-deployment.yaml | 1 + tests/unit/test_t2i_security.py | 9 +++--- tests/unit/test_websocket_security.py | 28 +++++++++++++++++++ 7 files changed, 63 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 42e99e1eb7..98fda7860f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ RUN python -m pip install uv \ && uv pip install -r requirements.txt --no-cache-dir --system \ && uv pip install socksio uv pilk --no-cache-dir --system +# Keep UID/GID 1000 in sync with the Kubernetes runAsUser/runAsGroup/fsGroup values. RUN groupadd --system --gid 1000 astrbot \ && useradd --system --uid 1000 --gid astrbot --home-dir /AstrBot --shell /usr/sbin/nologin astrbot \ && mkdir -p /AstrBot/data \ diff --git a/astrbot/core/platform/sources/misskey/misskey_api.py b/astrbot/core/platform/sources/misskey/misskey_api.py index 6757377ea9..d43eae16a5 100644 --- a/astrbot/core/platform/sources/misskey/misskey_api.py +++ b/astrbot/core/platform/sources/misskey/misskey_api.py @@ -15,7 +15,7 @@ from astrbot.api import logger -from ..websocket_security import require_secure_transport_url, to_websocket_url +from ..websocket_security import to_websocket_url from .misskey_utils import FileIDExtractor # Constants @@ -57,12 +57,7 @@ def __init__(self, instance_url: str, access_token: str) -> None: async def connect(self) -> bool: try: - require_secure_transport_url( - self.instance_url, - label="Misskey instance URL", - allowed_schemes={"http", "https", "ws", "wss"}, - ) - ws_url = to_websocket_url(self.instance_url) + ws_url = to_websocket_url(self.instance_url, label="Misskey instance URL") ws_url += f"/streaming?i={self.access_token}" self.websocket = await websockets.connect( diff --git a/astrbot/core/platform/sources/websocket_security.py b/astrbot/core/platform/sources/websocket_security.py index 8ca6fa41ba..cb6be0ff24 100644 --- a/astrbot/core/platform/sources/websocket_security.py +++ b/astrbot/core/platform/sources/websocket_security.py @@ -43,14 +43,36 @@ def require_secure_transport_url( return parsed -def to_websocket_url(url: str) -> str: - parsed = urlsplit(url.rstrip("/")) +def to_websocket_url(url: str, *, label: str = "WebSocket URL") -> str: + normalized_url = url.rstrip("/") + parsed = urlsplit(normalized_url) + allowed_schemes = {"http", "https", "ws", "wss"} + + if parsed.scheme not in allowed_schemes: + raise ValueError( + f"{label} must use http://, https://, ws:// or wss://: {normalized_url}", + ) + + parsed = require_secure_transport_url( + normalized_url, + label=label, + allowed_schemes=allowed_schemes, + ) scheme_map = { "http": "ws", "https": "wss", "ws": "ws", "wss": "wss", } + + try: + ws_scheme = scheme_map[parsed.scheme] + except KeyError as exc: + raise ValueError( + f"{label} must use http://, https://, ws:// or wss://: " + f"{normalized_url}", + ) from exc + return urlunsplit( - parsed._replace(scheme=scheme_map[parsed.scheme]), + parsed._replace(scheme=ws_scheme), ) diff --git a/k8s/astrbot/02-deployment.yaml b/k8s/astrbot/02-deployment.yaml index 812f170477..b7151a8a6e 100644 --- a/k8s/astrbot/02-deployment.yaml +++ b/k8s/astrbot/02-deployment.yaml @@ -18,6 +18,7 @@ spec: app: astrbot-standalone spec: securityContext: + # Keep these IDs in sync with the `astrbot` user declared in Dockerfile. runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 diff --git a/k8s/astrbot_with_napcat/02-deployment.yaml b/k8s/astrbot_with_napcat/02-deployment.yaml index 7beeca056a..3072c9a5f4 100644 --- a/k8s/astrbot_with_napcat/02-deployment.yaml +++ b/k8s/astrbot_with_napcat/02-deployment.yaml @@ -23,6 +23,7 @@ spec: # 优雅关闭时间,给 NapCat 足够时间保存状态 terminationGracePeriodSeconds: 60 securityContext: + # Keep these IDs in sync with the `astrbot` user declared in Dockerfile. runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 diff --git a/tests/unit/test_t2i_security.py b/tests/unit/test_t2i_security.py index e66a89fda6..4c17da215b 100644 --- a/tests/unit/test_t2i_security.py +++ b/tests/unit/test_t2i_security.py @@ -21,10 +21,11 @@ async def test_network_strategy_render_preserves_backticks(): def test_t2i_templates_use_json_serialization_for_text(): - template_paths = [ - Path("astrbot/core/utils/t2i/template/base.html"), - Path("astrbot/core/utils/t2i/template/astrbot_powershell.html"), - ] + template_paths = sorted( + Path("astrbot/core/utils/t2i/template").glob("*.html"), + ) + + assert template_paths for template_path in template_paths: content = template_path.read_text(encoding="utf-8") diff --git a/tests/unit/test_websocket_security.py b/tests/unit/test_websocket_security.py index 42a56b2316..bd637406cb 100644 --- a/tests/unit/test_websocket_security.py +++ b/tests/unit/test_websocket_security.py @@ -51,6 +51,34 @@ def test_to_websocket_url_converts_https_to_wss() -> None: ) +def test_to_websocket_url_rejects_unsupported_scheme() -> None: + with pytest.raises( + ValueError, + match="Misskey instance URL must use http://, https://, ws:// or wss://", + ): + to_websocket_url("ftp://example.com", label="Misskey instance URL") + + +@pytest.mark.asyncio +async def test_streaming_client_connects_with_secure_websocket_url() -> None: + client = StreamingClient("https://example.com", "token") + websocket = AsyncMock() + + with patch( + "astrbot.core.platform.sources.misskey.misskey_api.websockets.connect", + new_callable=AsyncMock, + ) as mock_connect: + mock_connect.return_value = websocket + + assert await client.connect() is True + + mock_connect.assert_awaited_once_with( + "wss://example.com/streaming?i=token", + ping_interval=30, + ping_timeout=10, + ) + + @pytest.mark.asyncio async def test_streaming_client_rejects_remote_http_instance() -> None: client = StreamingClient("http://example.com", "token") From a82d3de1e62accf5af4be180e37ac00437873cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:14:43 +0900 Subject: [PATCH 5/9] revert: restore NapCat documentation --- docs/en/platform/aiocqhttp/napcat.md | 4 ++-- docs/zh/platform/aiocqhttp/napcat.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/platform/aiocqhttp/napcat.md b/docs/en/platform/aiocqhttp/napcat.md index 49538e7218..bc081721a7 100644 --- a/docs/en/platform/aiocqhttp/napcat.md +++ b/docs/en/platform/aiocqhttp/napcat.md @@ -117,11 +117,11 @@ Switch back to NapCat's management panel, click `Network Configuration->New->Web In the newly opened window: - Check `Enable`. -- For local deployment, fill in `URL` with `ws://HostIP:Port/ws`. For example, `ws://localhost:6199/ws` or `ws://127.0.0.1:6199/ws`. +- Fill in `URL` with `ws://HostIP:Port/ws`. For example, `ws://localhost:6199/ws` or `ws://127.0.0.1:6199/ws`. > [!IMPORTANT] > 1. If deploying with Docker and both AstrBot and NapCat containers are connected to the same network, use `ws://astrbot:6199/ws` (refer to the Docker script in this documentation). -> 2. For cross-host or public network deployment, do not expose plain `ws://`. Put AstrBot behind an HTTPS/TLS reverse proxy and connect with `wss://your-domain/ws` instead. +> 2. Due to Docker network isolation, when not on the same network, please use the internal network IP address or public network IP address ***(unsafe)*** to connect, i.e., `ws://(internal/public IP):6199/ws`. - Message Format: `Array` - Heartbeat Interval: `5000` diff --git a/docs/zh/platform/aiocqhttp/napcat.md b/docs/zh/platform/aiocqhttp/napcat.md index ade384d387..240579a712 100644 --- a/docs/zh/platform/aiocqhttp/napcat.md +++ b/docs/zh/platform/aiocqhttp/napcat.md @@ -110,11 +110,11 @@ docker logs napcat 在新弹出的窗口中: - 勾选 `启用`。 -- 本机部署时,`URL` 填写 `ws://宿主机IP:端口/ws`。如 `ws://localhost:6199/ws` 或 `ws://127.0.0.1:6199/ws`。 +- `URL` 填写 `ws://宿主机IP:端口/ws`。如 `ws://localhost:6199/ws` 或 `ws://127.0.0.1:6199/ws`。 > [!IMPORTANT] > 1. 如果采用 Docker 部署并同时把 AstrBot 和 NapCat 两个容器接入了同一网络,`ws://astrbot:6199/ws`(参考本文档的 Docker 脚本)。 -> 2. 如果是跨主机或公网部署,请不要暴露明文 `ws://`。应通过 HTTPS/TLS 反向代理暴露 AstrBot,并使用 `wss://你的域名/ws` 连接。 +> 2. 由于 Docker 网络隔离的原因,不在同一个网络时请使用内网 IP 地址或公网 IP 地址 ***(不安全)*** 进行连接,即 `ws://(内网/公网):6199/ws`。 - 消息格式:`Array` - 心跳间隔: `5000` From bac50ca0110ea496aa23c3bff475b99ff96e25d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:18:36 +0900 Subject: [PATCH 6/9] revert: restore Dockerfile root runtime --- Dockerfile | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 98fda7860f..992060d6ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,14 +27,6 @@ RUN python -m pip install uv \ && uv pip install -r requirements.txt --no-cache-dir --system \ && uv pip install socksio uv pilk --no-cache-dir --system -# Keep UID/GID 1000 in sync with the Kubernetes runAsUser/runAsGroup/fsGroup values. -RUN groupadd --system --gid 1000 astrbot \ - && useradd --system --uid 1000 --gid astrbot --home-dir /AstrBot --shell /usr/sbin/nologin astrbot \ - && mkdir -p /AstrBot/data \ - && chown -R astrbot:astrbot /AstrBot - EXPOSE 6185 -USER astrbot - CMD ["python", "main.py"] From fb65eca6eb1c2466e9138f3601bc6103f58dc7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:22:07 +0900 Subject: [PATCH 7/9] style: format websocket security helper --- astrbot/core/platform/sources/websocket_security.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/astrbot/core/platform/sources/websocket_security.py b/astrbot/core/platform/sources/websocket_security.py index cb6be0ff24..1fa1e67c43 100644 --- a/astrbot/core/platform/sources/websocket_security.py +++ b/astrbot/core/platform/sources/websocket_security.py @@ -69,8 +69,7 @@ def to_websocket_url(url: str, *, label: str = "WebSocket URL") -> str: ws_scheme = scheme_map[parsed.scheme] except KeyError as exc: raise ValueError( - f"{label} must use http://, https://, ws:// or wss://: " - f"{normalized_url}", + f"{label} must use http://, https://, ws:// or wss://: {normalized_url}", ) from exc return urlunsplit( From e6eb69ed38d79c0b3a9b24c8f8a7ce7d133f3c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:31:23 +0900 Subject: [PATCH 8/9] fix: address security review follow-ups --- astrbot/core/computer/booters/local.py | 2 ++ astrbot/core/platform/sources/websocket_security.py | 6 +++--- docs/scripts/upload_doc_images_to_r2.py | 2 ++ tests/unit/test_sqlite_v3.py | 1 + tests/unit/test_websocket_security.py | 6 +++--- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/astrbot/core/computer/booters/local.py b/astrbot/core/computer/booters/local.py index 5dfb52c411..08e2d6b1e7 100644 --- a/astrbot/core/computer/booters/local.py +++ b/astrbot/core/computer/booters/local.py @@ -111,6 +111,8 @@ async def exec( ) -> dict[str, Any]: def _run() -> dict[str, Any]: try: + # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit + # Executes the current interpreter with a fixed argv list and shell=False. result = subprocess.run( [sys.executable, "-c", code], timeout=timeout, diff --git a/astrbot/core/platform/sources/websocket_security.py b/astrbot/core/platform/sources/websocket_security.py index 1fa1e67c43..47d5904830 100644 --- a/astrbot/core/platform/sources/websocket_security.py +++ b/astrbot/core/platform/sources/websocket_security.py @@ -37,7 +37,7 @@ def require_secure_transport_url( parsed.hostname ): raise ValueError( - f"{label} must use wss:// or https:// for non-local endpoints: {url}", + f"{label} must use secure transport (https or wss) for non-local endpoints: {url}", ) return parsed @@ -50,7 +50,7 @@ def to_websocket_url(url: str, *, label: str = "WebSocket URL") -> str: if parsed.scheme not in allowed_schemes: raise ValueError( - f"{label} must use http://, https://, ws:// or wss://: {normalized_url}", + f"{label} must use the http, https, ws, or wss scheme: {normalized_url}", ) parsed = require_secure_transport_url( @@ -69,7 +69,7 @@ def to_websocket_url(url: str, *, label: str = "WebSocket URL") -> str: ws_scheme = scheme_map[parsed.scheme] except KeyError as exc: raise ValueError( - f"{label} must use http://, https://, ws:// or wss://: {normalized_url}", + f"{label} must use the http, https, ws, or wss scheme: {normalized_url}", ) from exc return urlunsplit( diff --git a/docs/scripts/upload_doc_images_to_r2.py b/docs/scripts/upload_doc_images_to_r2.py index f23a1205bf..23df4d485b 100755 --- a/docs/scripts/upload_doc_images_to_r2.py +++ b/docs/scripts/upload_doc_images_to_r2.py @@ -220,6 +220,8 @@ def run_rclone_upload( else: print(f"Uploading to: {target}") + # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit + # Uses an argv list with shell=False after checking that rclone exists in PATH. subprocess.run(cmd, check=True, shell=False) finally: tmp_path.unlink(missing_ok=True) diff --git a/tests/unit/test_sqlite_v3.py b/tests/unit/test_sqlite_v3.py index 7708d54057..3b14f6bbad 100644 --- a/tests/unit/test_sqlite_v3.py +++ b/tests/unit/test_sqlite_v3.py @@ -69,6 +69,7 @@ def test_get_filtered_conversations_uses_named_parameters(self): assert count_params["exclude_id_0"] == "admin%" assert count_params["exclude_platform_0"] == "slack:%" + assert "FROM webchat_conversation WHERE" in data_sql assert ":page_size" in data_sql assert ":offset" in data_sql assert data_params["page_size"] == 10 diff --git a/tests/unit/test_websocket_security.py b/tests/unit/test_websocket_security.py index bd637406cb..c979754ab2 100644 --- a/tests/unit/test_websocket_security.py +++ b/tests/unit/test_websocket_security.py @@ -22,7 +22,7 @@ def test_require_secure_transport_url_allows_local_ws() -> None: def test_require_secure_transport_url_rejects_public_ws() -> None: with pytest.raises( ValueError, - match="must use wss:// or https:// for non-local endpoints", + match=r"must use secure transport \(https or wss\) for non-local endpoints", ): require_secure_transport_url( "ws://example.com/events", @@ -34,7 +34,7 @@ def test_require_secure_transport_url_rejects_public_ws() -> None: def test_require_secure_transport_url_rejects_bare_hostname_ws() -> None: with pytest.raises( ValueError, - match="must use wss:// or https:// for non-local endpoints", + match=r"must use secure transport \(https or wss\) for non-local endpoints", ): require_secure_transport_url( "ws://prod/events", @@ -54,7 +54,7 @@ def test_to_websocket_url_converts_https_to_wss() -> None: def test_to_websocket_url_rejects_unsupported_scheme() -> None: with pytest.raises( ValueError, - match="Misskey instance URL must use http://, https://, ws:// or wss://", + match="Misskey instance URL must use the http, https, ws, or wss scheme", ): to_websocket_url("ftp://example.com", label="Misskey instance URL") From 9ffe3fc658ec69cf18f643f10469b5da42707756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 12 Mar 2026 19:39:42 +0900 Subject: [PATCH 9/9] test: strengthen sqlite injection assertions --- tests/unit/test_sqlite_v3.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_sqlite_v3.py b/tests/unit/test_sqlite_v3.py index 3b14f6bbad..203faaacd9 100644 --- a/tests/unit/test_sqlite_v3.py +++ b/tests/unit/test_sqlite_v3.py @@ -43,13 +43,14 @@ def test_get_grouped_base_stats_uses_bound_parameter(self): def test_get_filtered_conversations_uses_named_parameters(self): db, cursor = self._create_db_with_mock_cursor() + unsafe_search = "x' OR 1=1 --" db.get_filtered_conversations( page=2, page_size=10, platforms=["qq"], message_types=["group"], - search_query="x' OR 1=1 --", + search_query=unsafe_search, exclude_ids=["admin"], exclude_platforms=["slack"], ) @@ -62,15 +63,17 @@ def test_get_filtered_conversations_uses_named_parameters(self): assert ":search_query" in count_sql assert ":exclude_id_0" in count_sql assert ":exclude_platform_0" in count_sql - assert "x' OR 1=1 --" not in count_sql + assert unsafe_search not in count_sql assert count_params["platform_0"] == "qq:%" assert count_params["message_type_0"] == "%:group:%" - assert count_params["search_query"] == "%x' OR 1=1 --%" + assert unsafe_search in count_params["search_query"] assert count_params["exclude_id_0"] == "admin%" assert count_params["exclude_platform_0"] == "slack:%" assert "FROM webchat_conversation WHERE" in data_sql + assert unsafe_search not in data_sql assert ":page_size" in data_sql assert ":offset" in data_sql + assert unsafe_search in data_params["search_query"] assert data_params["page_size"] == 10 assert data_params["offset"] == 10