fix: 修复XSS漏洞、内存泄漏、超时处理等安全问题

- 聊天消息HTML转义防止XSS注入
- 匿名会话使用唯一ID替代共享portal-guest
- 上下文Map添加LRU淘汰和TTL过期清理
- 修复Node.js https超时处理使用req.setTimeout()
- MCP工具数从API动态获取替代硬编码

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/576c1b97-3ce4-4c28-99f1-9374e2320212

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-11 09:31:39 +00:00 committed by GitHub
parent d7c613ce1a
commit fdc2ac0035
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 12 deletions

View File

@ -168,8 +168,7 @@ function callDomesticLLM(modelConfig, messages) {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'Content-Length': Buffer.byteLength(requestBody)
},
timeout: 60000
}
};
const req = https.request(options, (res) => {
@ -189,8 +188,8 @@ function callDomesticLLM(modelConfig, messages) {
});
});
req.setTimeout(60000, () => { req.destroy(); reject(new Error('请求超时(60s)')); });
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('请求超时(60s)')); });
req.write(requestBody);
req.end();
});
@ -230,14 +229,36 @@ const PERSONA_SYSTEM_PROMPT = `你是铸渊Zhùyuān光湖语言世界
// ─── 上下文管理 ───
const contexts = new Map();
const MAX_HISTORY = 20;
const MAX_CONTEXTS = 500; // 最大会话数
const CONTEXT_TTL_MS = 3600000; // 1小时过期
function getContext(userId) {
if (!contexts.has(userId)) {
contexts.set(userId, { messages: [], count: 0, created: Date.now() });
// 超过上限时清理最老的会话
if (contexts.size >= MAX_CONTEXTS) {
let oldest = null, oldestKey = null;
for (const [key, val] of contexts) {
if (!oldest || val.created < oldest) { oldest = val.created; oldestKey = key; }
}
if (oldestKey) contexts.delete(oldestKey);
}
contexts.set(userId, { messages: [], count: 0, created: Date.now(), lastActive: Date.now() });
}
return contexts.get(userId);
const ctx = contexts.get(userId);
ctx.lastActive = Date.now();
return ctx;
}
// 定期清理过期会话
setInterval(() => {
const now = Date.now();
for (const [key, val] of contexts) {
if (now - val.lastActive > CONTEXT_TTL_MS) {
contexts.delete(key);
}
}
}, 300000); // 每5分钟清理一次
/**
* 国内模型智能对话带自动降级
*/

View File

@ -476,8 +476,12 @@ const CHANNELS=[
</div>`).join('');
})();
// ═══ 聊天面板 ═══
// ═══ 聊天面板 ═══
let chatOpen=false;
// 生成唯一匿名会话ID
const portalSessionId='portal-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,8);
function toggleChat(){
chatOpen=!chatOpen;
document.getElementById('chatPanel').classList.toggle('open',chatOpen);
@ -503,7 +507,7 @@ async function sendMessage(){
const res=await fetch('/api/chat',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:msg,userId:'portal-guest'})
body:JSON.stringify({message:msg,userId:portalSessionId})
});
const data=await res.json();
typing.remove();
@ -523,9 +527,14 @@ function appendMsg(role,text,model){
const body=document.getElementById('chatBody');
const div=document.createElement('div');
div.className='chat-msg '+role;
// 转义HTML防止XSS
const escaped=text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');
let meta='';
if(role==='bot')meta=`<div class="msg-meta">铸渊${model?' · '+model:''}</div>`;
div.innerHTML=text.replace(/\n/g,'<br>')+meta;
if(role==='bot'){
const safeModel=model?(model.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')):'';
meta=`<div class="msg-meta">铸渊${safeModel?' · '+safeModel:''}</div>`;
}
div.innerHTML=escaped+meta;
body.appendChild(div);
scrollChat();
}

View File

@ -623,9 +623,14 @@ function appendChatMsg(role,text,model){
const box=document.getElementById('chatMessages');
const div=document.createElement('div');
div.className='chat-msg '+role;
// 转义HTML防止XSS
const escaped=text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');
let info='';
if(role==='bot')info=`<div class="msg-info"><span>铸渊 · ICE-GL-ZY001</span><span>${model||currentModelLine==='domestic'?'智能路由':'第三方'}</span></div>`;
div.innerHTML=text.replace(/\n/g,'<br>')+info;
if(role==='bot'){
const safeModel=model?(model.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')):'';
info=`<div class="msg-info"><span>铸渊 · ICE-GL-ZY001</span><span>${safeModel||((currentModelLine==='domestic')?'智能路由':'第三方')}</span></div>`;
}
div.innerHTML=escaped+info;
box.appendChild(div);
scrollChat();
}
@ -694,8 +699,8 @@ function loadServices(){
`<div class="svc-item"><span class="svc-dot green"></span><span class="svc-name">${e.name}</span><span class="svc-port">:${e.port}</span></div>`
).join('');
// MCP 工具数
document.getElementById('dashMcpCount').textContent='121';
// MCP 工具数 — 从API获取
fetchMcpCount();
// 最近日志
const now=new Date();
@ -736,6 +741,18 @@ function loadPersonas(){
function formatTime(d){return d.toTimeString().slice(0,5)}
// ═══ MCP 工具数获取 ═══
async function fetchMcpCount(){
try{
const res=await fetch('/api/mcp/health');
const d=await res.json();
const count=d.tools_count||d.totalTools||'--';
document.getElementById('dashMcpCount').textContent=String(count);
}catch(e){
document.getElementById('dashMcpCount').textContent='--';
}
}
// ═══ Notion 状态检测 ═══
async function checkNotion(){
try{