From 6e9be01a099f68c8d2a5c5cc1b8c7dbdbcd19b6c Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 29 Mar 2026 04:27:18 +0000 Subject: [PATCH] =?UTF-8?q?security:=20=E5=A2=9E=E5=BC=BA=20server.js=20?= =?UTF-8?q?=E5=92=8C=20view-doc.html=20=E7=9A=84=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=80=A7=E4=B8=8E=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit server.js 改进: - 使用标准 URL API 代替简单的 split('?')[0] - 添加路径遍历攻击防护(path.normalize + 路径检查) - 改进错误处理,不暴露内部错误详情 - 添加 try-catch 保护 URL 解析 view-doc.html 改进: - markdown-it 配置 html: false 防止 XSS 攻击 - 添加文档路径安全验证(检查 .. / \ 字符) - 使用并行请求代替串行(Promise.all) - 使用 textContent 设置标题防止 XSS - 优化错误提示信息 Co-Authored-By: Claude Sonnet 4.6 --- prototype/server.js | 34 ++++++++++++++++++++++++------- prototype/view-doc.html | 45 +++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/prototype/server.js b/prototype/server.js index 0f65b96..423c892 100644 --- a/prototype/server.js +++ b/prototype/server.js @@ -19,6 +19,7 @@ const http = require('http'); const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); +const { URL } = require('url'); const PORT = 8080; const PROTOTYPE_DIR = __dirname; @@ -44,16 +45,35 @@ const MIME_TYPES = { // 创建服务器 const server = http.createServer((req, res) => { - // 解析 URL - let urlPath = req.url.split('?')[0] || '/'; + // 使用 URL 模块安全解析 URL + let urlPath; + try { + const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`); + urlPath = parsedUrl.pathname || '/'; + } catch { + urlPath = '/'; + } // 处理根路径 if (urlPath === '/') { urlPath = '/index.html'; } - // 构建文件路径 - const filePath = urlPath.startsWith("/planning-artifacts/") ? path.join(PARENT_DIR, urlPath) : path.join(PROTOTYPE_DIR, urlPath); + // 安全验证:防止路径遍历攻击 + // 规范化路径并检查是否在允许的目录内 + const isPlanningArtifacts = urlPath.startsWith("/planning-artifacts/"); + const baseDir = isPlanningArtifacts ? PARENT_DIR : PROTOTYPE_DIR; + const relativePath = isPlanningArtifacts ? urlPath : urlPath; + + const normalizedPath = path.normalize(path.join(baseDir, relativePath)); + if (!normalizedPath.startsWith(baseDir)) { + // 路径遍历攻击检测 + res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end('400 - 错误请求

400 - 错误请求

无效的路径

'); + return; + } + + const filePath = normalizedPath; const extname = path.extname(filePath).toLowerCase(); // 设置响应头 @@ -104,16 +124,16 @@ const server = http.createServer((req, res) => {

404

-

文件未找到: ${urlPath}

+

文件未找到

返回首页
`); } else { - // 服务器错误 + // 服务器错误 - 不暴露内部错误详情 res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end('服务器错误: ' + err.code); + res.end('500 - 服务器错误

500 - 服务器错误

服务器内部错误,请稍后重试

'); } } else { // 成功返回文件 diff --git a/prototype/view-doc.html b/prototype/view-doc.html index f6dd822..71db66e 100644 --- a/prototype/view-doc.html +++ b/prototype/view-doc.html @@ -191,9 +191,9 @@