diff --git a/persona-studio/backend/brain/memory-injector.js b/persona-studio/backend/brain/memory-injector.js index c37b81d5..78f48b6c 100644 --- a/persona-studio/backend/brain/memory-injector.js +++ b/persona-studio/backend/brain/memory-injector.js @@ -129,7 +129,9 @@ function loadProfile(devId) { } /** - * 粗略估算 token 数量(中文约 1.5 token/字,英文约 0.25 token/word) + * 粗略估算 token 数量 + * 中文约 1.5 token/字(CJK字符经 BPE 分词通常为 1-2 token) + * 英文/其他约 0.4 token/字符(英文单词平均 4 字符 ≈ 1 token) */ function estimateTokens(text) { if (!text) return 0; diff --git a/persona-studio/backend/routes/build.js b/persona-studio/backend/routes/build.js index 12b2c575..a60dfb18 100644 --- a/persona-studio/backend/routes/build.js +++ b/persona-studio/backend/routes/build.js @@ -40,6 +40,9 @@ router.post('/start', async (req, res) => { }); } + // 联系方式输入清理(去除潜在 HTML/script 注入) + const safeContact = contact ? String(contact).replace(/[<>&"']/g, '').substring(0, 100) : null; + // 先立即响应,后台异步处理 res.json({ error: false, @@ -68,7 +71,7 @@ router.post('/start', async (req, res) => { memoryManager.addProject(dev_id, { name: result.projectName || 'untitled', email: email, - contact: contact || null, + contact: safeContact, status: 'completed', created_at: new Date().toISOString(), files: result.files || [] diff --git a/persona-studio/backend/routes/preview.js b/persona-studio/backend/routes/preview.js index 1b310dbc..f3a68385 100644 --- a/persona-studio/backend/routes/preview.js +++ b/persona-studio/backend/routes/preview.js @@ -21,9 +21,18 @@ router.get('/:devId/:project', (req, res) => { }); } - // 安全校验:防止路径遍历 + // 安全校验:确保 devId 和 project 匹配预期格式(防止路径遍历) const safeDevId = path.basename(devId); const safeProject = path.basename(project); + + if (!/^[a-zA-Z0-9_-]+$/.test(safeDevId) || !/^[a-zA-Z0-9_.-]+$/.test(safeProject)) { + return res.status(400).json({ + error: true, + code: 'INVALID_PARAMS', + message: '参数格式无效' + }); + } + const previewDir = path.join(WORKSPACE_DIR, safeDevId, safeProject, 'preview'); const projectDir = path.join(WORKSPACE_DIR, safeDevId, safeProject); @@ -53,6 +62,10 @@ router.get('/:devId/:project/:file', (req, res) => { const safeProject = path.basename(project); const safeFile = path.basename(file); + if (!/^[a-zA-Z0-9_-]+$/.test(safeDevId) || !/^[a-zA-Z0-9_.-]+$/.test(safeProject) || !/^[a-zA-Z0-9_.-]+$/.test(safeFile)) { + return res.status(400).send('Invalid parameters'); + } + const previewDir = path.join(WORKSPACE_DIR, safeDevId, safeProject, 'preview'); const projectDir = path.join(WORKSPACE_DIR, safeDevId, safeProject); diff --git a/persona-studio/backend/server.js b/persona-studio/backend/server.js index b3bca0e8..a816f5ba 100644 --- a/persona-studio/backend/server.js +++ b/persona-studio/backend/server.js @@ -117,6 +117,7 @@ try { }; } catch (_e) { // ws module not installed, WebSocket disabled + console.warn('[Persona Studio] ws module not available, WebSocket features disabled'); app.locals.broadcastToClient = function () {}; } diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js index ed1c460a..44bbb726 100644 --- a/persona-studio/frontend/chat.js +++ b/persona-studio/frontend/chat.js @@ -606,7 +606,9 @@ function closeEmailModal() { } /** - * 后端二次校验用的邮箱正则 + * 前端邮箱格式校验(与后端 build.js 使用相同正则,双重校验) + * @param {string} email - 邮箱地址 + * @returns {boolean} 是否合法 */ function validateEmail(email) { var re = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;