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:
parent
d7c613ce1a
commit
fdc2ac0035
|
|
@ -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分钟清理一次
|
||||
|
||||
/**
|
||||
* 国内模型智能对话(带自动降级)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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,'&').replace(/</g,'<').replace(/>/g,'>').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,'&').replace(/</g,'<').replace(/>/g,'>')):'';
|
||||
meta=`<div class="msg-meta">铸渊${safeModel?' · '+safeModel:''}</div>`;
|
||||
}
|
||||
div.innerHTML=escaped+meta;
|
||||
body.appendChild(div);
|
||||
scrollChat();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,'&').replace(/</g,'<').replace(/>/g,'>').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,'&').replace(/</g,'<').replace(/>/g,'>')):'';
|
||||
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{
|
||||
|
|
|
|||
Loading…
Reference in New Issue