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',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${apiKey}`,
|
'Authorization': `Bearer ${apiKey}`,
|
||||||
'Content-Length': Buffer.byteLength(requestBody)
|
'Content-Length': Buffer.byteLength(requestBody)
|
||||||
},
|
}
|
||||||
timeout: 60000
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const req = https.request(options, (res) => {
|
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('error', reject);
|
||||||
req.on('timeout', () => { req.destroy(); reject(new Error('请求超时(60s)')); });
|
|
||||||
req.write(requestBody);
|
req.write(requestBody);
|
||||||
req.end();
|
req.end();
|
||||||
});
|
});
|
||||||
|
|
@ -230,14 +229,36 @@ const PERSONA_SYSTEM_PROMPT = `你是铸渊(Zhùyuān),光湖语言世界
|
||||||
// ─── 上下文管理 ───
|
// ─── 上下文管理 ───
|
||||||
const contexts = new Map();
|
const contexts = new Map();
|
||||||
const MAX_HISTORY = 20;
|
const MAX_HISTORY = 20;
|
||||||
|
const MAX_CONTEXTS = 500; // 最大会话数
|
||||||
|
const CONTEXT_TTL_MS = 3600000; // 1小时过期
|
||||||
|
|
||||||
function getContext(userId) {
|
function getContext(userId) {
|
||||||
if (!contexts.has(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('');
|
</div>`).join('');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// ═══ 聊天面板 ═══
|
||||||
// ═══ 聊天面板 ═══
|
// ═══ 聊天面板 ═══
|
||||||
let chatOpen=false;
|
let chatOpen=false;
|
||||||
|
// 生成唯一匿名会话ID
|
||||||
|
const portalSessionId='portal-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,8);
|
||||||
|
|
||||||
function toggleChat(){
|
function toggleChat(){
|
||||||
chatOpen=!chatOpen;
|
chatOpen=!chatOpen;
|
||||||
document.getElementById('chatPanel').classList.toggle('open',chatOpen);
|
document.getElementById('chatPanel').classList.toggle('open',chatOpen);
|
||||||
|
|
@ -503,7 +507,7 @@ async function sendMessage(){
|
||||||
const res=await fetch('/api/chat',{
|
const res=await fetch('/api/chat',{
|
||||||
method:'POST',
|
method:'POST',
|
||||||
headers:{'Content-Type':'application/json'},
|
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();
|
const data=await res.json();
|
||||||
typing.remove();
|
typing.remove();
|
||||||
|
|
@ -523,9 +527,14 @@ function appendMsg(role,text,model){
|
||||||
const body=document.getElementById('chatBody');
|
const body=document.getElementById('chatBody');
|
||||||
const div=document.createElement('div');
|
const div=document.createElement('div');
|
||||||
div.className='chat-msg '+role;
|
div.className='chat-msg '+role;
|
||||||
|
// 转义HTML防止XSS
|
||||||
|
const escaped=text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>');
|
||||||
let meta='';
|
let meta='';
|
||||||
if(role==='bot')meta=`<div class="msg-meta">铸渊${model?' · '+model:''}</div>`;
|
if(role==='bot'){
|
||||||
div.innerHTML=text.replace(/\n/g,'<br>')+meta;
|
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);
|
body.appendChild(div);
|
||||||
scrollChat();
|
scrollChat();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -623,9 +623,14 @@ function appendChatMsg(role,text,model){
|
||||||
const box=document.getElementById('chatMessages');
|
const box=document.getElementById('chatMessages');
|
||||||
const div=document.createElement('div');
|
const div=document.createElement('div');
|
||||||
div.className='chat-msg '+role;
|
div.className='chat-msg '+role;
|
||||||
|
// 转义HTML防止XSS
|
||||||
|
const escaped=text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>');
|
||||||
let info='';
|
let info='';
|
||||||
if(role==='bot')info=`<div class="msg-info"><span>铸渊 · ICE-GL-ZY001</span><span>${model||currentModelLine==='domestic'?'智能路由':'第三方'}</span></div>`;
|
if(role==='bot'){
|
||||||
div.innerHTML=text.replace(/\n/g,'<br>')+info;
|
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);
|
box.appendChild(div);
|
||||||
scrollChat();
|
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>`
|
`<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('');
|
).join('');
|
||||||
|
|
||||||
// MCP 工具数
|
// MCP 工具数 — 从API获取
|
||||||
document.getElementById('dashMcpCount').textContent='121';
|
fetchMcpCount();
|
||||||
|
|
||||||
// 最近日志
|
// 最近日志
|
||||||
const now=new Date();
|
const now=new Date();
|
||||||
|
|
@ -736,6 +741,18 @@ function loadPersonas(){
|
||||||
|
|
||||||
function formatTime(d){return d.toTimeString().slice(0,5)}
|
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 状态检测 ═══
|
// ═══ Notion 状态检测 ═══
|
||||||
async function checkNotion(){
|
async function checkNotion(){
|
||||||
try{
|
try{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue