From 394638d426efb92ee42588bb97584b0820b0b6b3 Mon Sep 17 00:00:00 2001 From: Yo-LRK Date: Sat, 13 Jun 2026 17:32:36 +0700 Subject: [PATCH] security: address HIGH findings (stored XSS, wildcard CORS, prod log leak) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit H1 — stored XSS via v-html / innerHTML of unsanitized LLM/agent/interview/report content: add DOMPurify; both renderMarkdown() now return DOMPurify.sanitize(html) (Step4Report.vue, Step5Interaction.vue), and the formatAnswer innerHTML sink is wrapped in DOMPurify.sanitize. All 8 HTML-injection sinks now sanitized; markdown rendering preserved (DOMPurify secure defaults keep the md-* tags/classes). H4 — wildcard CORS: CORS origins now Config.ALLOWED_ORIGINS (comma-separated env, default localhost:3000) instead of '*'. H5 — request bodies written to disk in cleartext: logger file level now follows FLASK_DEBUG (INFO in prod), so the before_request body-debug log is suppressed in production. (traceback-in-response across 53 handlers deferred to a separate refactor.) Co-Authored-By: Claude Opus 4.8 --- .env.example | 4 ++++ backend/app/__init__.py | 4 ++-- backend/app/config.py | 8 ++++++++ backend/app/utils/logger.py | 16 ++++++++++----- frontend/package-lock.json | 21 ++++++++++++++++---- frontend/package.json | 1 + frontend/src/components/Step4Report.vue | 17 ++++++++++------ frontend/src/components/Step5Interaction.vue | 4 +++- 8 files changed, 57 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index 5d90cd32..2aa57f90 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,10 @@ API_KEY=change_me_to_a_strong_api_key # 使打包后的 UI 自动带上 X-API-Key。注意:它会被打进客户端包、可被任何访问者提取(见 README 安全说明)。 VITE_API_KEY=change_me_to_a_strong_api_key +# ===== CORS 允许来源(H4)===== +# 逗号分隔的前端来源;不再用通配 '*'。生产填前端域名,例如 https://app.example.com +ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 + # ===== 模拟成本上限(C3,denial-of-wallet 防护)===== # 客户端未传 max_rounds 时的默认轮数上限(完整长度模拟请按请求传 max_rounds 或调高此值) # 默认 150 覆盖典型配置(72h/30min=144 轮)以免悄悄截断标准演示 diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 39547326..c2e30229 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -47,8 +47,8 @@ def create_app(config_class=Config): logger.info("MiroFish Backend 启动中...") logger.info("=" * 50) - # 启用CORS - CORS(app, resources={r"/api/*": {"origins": "*"}}) + # 启用CORS(H4):限定来源为 Config.ALLOWED_ORIGINS(默认本地前端源),不再通配 '*'。 + CORS(app, resources={r"/api/*": {"origins": Config.ALLOWED_ORIGINS}}) # API Key 鉴权(C2):所有 /api/* 端点强制鉴权。 # 客户端通过 `X-API-Key: ` 或 `Authorization: Bearer ` 传入。 diff --git a/backend/app/config.py b/backend/app/config.py index 0a6b90dd..5c27a3a9 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -34,6 +34,14 @@ class Config: API_KEY = os.environ.get('API_KEY') AUTH_ENABLED = os.environ.get('AUTH_ENABLED', 'true').strip().lower() not in ('false', '0', 'no', 'off') + # CORS 允许来源(H4):不再用通配 '*'。默认仅本地前端开发/预览源;生产用逗号分隔的 + # ALLOWED_ORIGINS 指定前端域名(例如 https://app.example.com)。'*' 仍可显式设置但不推荐。 + ALLOWED_ORIGINS = [ + o.strip() for o in os.environ.get( + 'ALLOWED_ORIGINS', 'http://localhost:3000,http://127.0.0.1:3000' + ).split(',') if o.strip() + ] + # JSON配置 - 禁用ASCII转义,让中文直接显示(而不是 \uXXXX 格式) JSON_AS_ASCII = False diff --git a/backend/app/utils/logger.py b/backend/app/utils/logger.py index 93422afa..ee4e7d12 100644 --- a/backend/app/utils/logger.py +++ b/backend/app/utils/logger.py @@ -27,17 +27,23 @@ def _ensure_utf8_stdout(): LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'logs') -def setup_logger(name: str = 'mirofish', level: int = logging.DEBUG) -> logging.Logger: +def setup_logger(name: str = 'mirofish', level: int = None) -> logging.Logger: """ 设置日志器 - + Args: name: 日志器名称 - level: 日志级别 - + level: 日志级别(None 时按 FLASK_DEBUG 自动选择) + Returns: 配置好的日志器 """ + # H5:生产模式(FLASK_DEBUG=false)下用 INFO —— 避免把请求体/调试细节(可能含上传内容、 + # 提示词、客户端传入的凭据)以明文写入轮转日志文件。开发模式仍用 DEBUG。 + if level is None: + from ..config import Config + level = logging.DEBUG if Config.DEBUG else logging.INFO + # 确保日志目录存在 os.makedirs(LOG_DIR, exist_ok=True) @@ -71,7 +77,7 @@ def setup_logger(name: str = 'mirofish', level: int = logging.DEBUG) -> logging. backupCount=5, encoding='utf-8' ) - file_handler.setLevel(logging.DEBUG) + file_handler.setLevel(level) file_handler.setFormatter(detailed_formatter) # 2. 控制台处理器 - 简洁日志(INFO及以上) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3e56d752..7570ce38 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "axios": "^1.14.0", "d3": "^7.9.0", + "dompurify": "^3.4.10", "vue": "^3.5.24", "vue-i18n": "^11.3.0", "vue-router": "^4.6.3" @@ -938,6 +939,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.2.tgz", @@ -1435,7 +1443,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -1538,6 +1545,15 @@ "node": ">=0.4.0" } }, + "node_modules/dompurify": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", + "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1913,7 +1929,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2053,7 +2068,6 @@ "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -2128,7 +2142,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz", "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.25", "@vue/compiler-sfc": "3.5.25", diff --git a/frontend/package.json b/frontend/package.json index 1501b628..65bcdacb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.14.0", "d3": "^7.9.0", + "dompurify": "^3.4.10", "vue": "^3.5.24", "vue-i18n": "^11.3.0", "vue-router": "^4.6.3" diff --git a/frontend/src/components/Step4Report.vue b/frontend/src/components/Step4Report.vue index 8e53ceb5..402092fb 100644 --- a/frontend/src/components/Step4Report.vue +++ b/frontend/src/components/Step4Report.vue @@ -391,6 +391,7 @@