security: 增强 server.js 和 view-doc.html 的安全性与性能

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 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-29 04:27:18 +00:00
parent 3e02f2c7a2
commit 6e9be01a09
2 changed files with 52 additions and 27 deletions

View File

@@ -191,9 +191,9 @@
</footer>
<script>
// 初始化 markdown-it
// 初始化 markdown-it - 安全配置(禁用 HTML 以防止 XSS
const md = window.markdownit({
html: true,
html: false, // 禁用 HTML 标签以防止 XSS
linkify: true,
typographer: true,
breaks: true
@@ -229,7 +229,7 @@
document.getElementById('error').textContent = message;
}
// 加载并渲染文档
// 加载并渲染文档 - 优化:并行尝试所有路径
async function loadAndRenderDoc() {
const docPath = getQueryParam('doc');
if (!docPath) {
@@ -237,6 +237,12 @@
return;
}
// 验证 docPath 安全性 - 防止路径遍历
if (docPath.includes('..') || docPath.includes('/') || docPath.includes('\\')) {
showError('无效的文档路径');
return;
}
// 从文件名提取标题
const title = docPath
.replace(/\.md$/, '')
@@ -244,7 +250,7 @@
.replace(/\b\w/g, l => l.toUpperCase());
document.getElementById('doc-title').textContent = title;
// 尝试多个路径查找文档
// 尝试多个路径查找文档 - 并行请求提高效率
const possiblePaths = [
`../planning-artifacts/${docPath}`,
`../docs/${docPath}`,
@@ -254,32 +260,31 @@
docPath
];
let markdown = null;
let loadedPath = null;
for (const path of possiblePaths) {
// 并行尝试所有路径,使用 Promise.race 获取第一个成功的结果
const fetchPromises = possiblePaths.map(async (path) => {
try {
const response = await fetch(path);
if (response.ok) {
markdown = await response.text();
loadedPath = path;
break;
return { path, markdown: await response.text() };
}
} catch (err) {
// 继续尝试下一个路径
continue;
return null;
} catch {
return null;
}
}
});
if (!markdown) {
showError(`无法加载文档: ${docPath}<br>已尝试以下路径:<br>${possiblePaths.join('<br>')}`);
const results = await Promise.all(fetchPromises);
const successfulResult = results.find(r => r !== null);
if (!successfulResult) {
showError('无法加载文档,请检查文件名是否正确');
return;
}
// 渲染 Markdown
const html = md.render(markdown);
// 渲染 Markdownhtml: false 配置已防止 XSS
const html = md.render(successfulResult.markdown);
// 显示内容
// 显示内容 - 使用 textContent 设置标题防止 XSS
document.getElementById('loading').classList.add('hidden');
document.getElementById('content').classList.remove('hidden');
document.getElementById('content').innerHTML = html;