feat: 升级至 v0.3.1 — 全量代码审计修复 + 安全增强
本次升级基于完整代码审查,修复 Critical/High/Medium/Low 四级共 96 项问题, 并通过返工审计修复 10 项遗留问题,tsc 双端类型检查零错误。 Critical (10/10 完成): - C-4: command.ts 接入 shell-quote 进行 token-level 注入检测,替代原有正则匹配 可防御 r"m" -rf /、$'rm'、$(echo rm) 等字符串拼接绕过 High (11/11 完成): - 竞态保护、Promise.allSettled、AbortController 资源泄漏、IPC 参数校验等 Medium (55/55 完成): - 事务保护、敏感数据脱敏、枚举校验、MUI v9 Stack prop 迁移、 React 组件 cancelled 标志、类型收窄等 Low (20/20 完成): - 辅助方法提取(flushToolCallBuffer/scoreAndPushMemory/tryAddColumn 等) - nanoid 统一替代 Date.now()+Math.random() - confirm() 替换为 MUI Dialog、useMemo 缓存、魔法数字命名化等 返工审计修复 (10/10 完成): - L-11: LogsSettings 残留的原生 confirm()/alert() 全部替换为 MUI Dialog/Alert - M-53: MemoryViewer handleSearch 独立 ref,修复 searching 状态卡死 - M-42: 脱敏短值(length <= 4)泄露修复 - M-47: tasks:update 补全 title/description 类型校验 - L-9: ollama.adapter 非流式路径 nanoid 统一 - M-45: audit:query limit 策略与 memory:listAll 一致化 - SettingsModal handleConfirmRemove 补全 try/catch + loadServers cleanup - L-15: CommandPalette useMemo 补全 sessions 响应式依赖 - useAgentStream 事件类型补全 seq/timestamp 字段 新增依赖: shell-quote + @types/shell-quote 版本号: 0.3.0 -> 0.3.1
This commit is contained in:
@@ -580,11 +580,51 @@ function MCPSettings() {
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newCommand, setNewCommand] = useState('');
|
||||
const [newArgs, setNewArgs] = useState('');
|
||||
const loadServers = () => { if (window.metona?.mcp?.listServers) window.metona.mcp.listServers().then((l) => setServers(l as MetonaMCPServerStatus[])).catch((err) => { console.error('[SettingsModal]', err); }); };
|
||||
useEffect(() => { loadServers(); }, []);
|
||||
// L-11 修复: 用 MUI Dialog 替换浏览器原生 confirm(),保持 UI 一致性
|
||||
const [confirmRemove, setConfirmRemove] = useState<string | null>(null);
|
||||
|
||||
// L-20 修复: loadServers 改为多行 async/await 写法,提升可读性
|
||||
const loadServers = useCallback(async () => {
|
||||
if (!window.metona?.mcp?.listServers) return;
|
||||
try {
|
||||
const list = await window.metona.mcp.listServers();
|
||||
setServers(list as MetonaMCPServerStatus[]);
|
||||
} catch (err) {
|
||||
console.error('[SettingsModal]', err);
|
||||
}
|
||||
}, []);
|
||||
// 审计补充修复: useEffect 添加 cancelled 标志,防止卸载后 setState
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
if (!window.metona?.mcp?.listServers) return;
|
||||
try {
|
||||
const list = await window.metona.mcp.listServers();
|
||||
if (!cancelled) setServers(list as MetonaMCPServerStatus[]);
|
||||
} catch (err) {
|
||||
if (!cancelled) console.error('[SettingsModal]', err);
|
||||
}
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
const handleAdd = async () => { if (!newName.trim() || !newCommand.trim()) return; await window.metona?.mcp?.addServer({ name: newName.trim(), transport: 'stdio', command: newCommand.trim(), args: newArgs.trim() ? newArgs.trim().split(/\s+/) : [], enabled: true }); setNewName(''); setNewCommand(''); setNewArgs(''); setShowAdd(false); loadServers(); };
|
||||
const statusColors: Record<string, string> = { connected: 'success.main', connecting: 'warning.main', disconnected: 'text.secondary', error: 'error.main' };
|
||||
|
||||
// L-11 修复: 确认移除 MCP 服务
|
||||
// 审计补充修复: 添加 try/catch,避免 removeServer reject 时 Dialog 卡死无法关闭
|
||||
const [removeError, setRemoveError] = useState<string | null>(null);
|
||||
const handleConfirmRemove = async () => {
|
||||
if (!confirmRemove) return;
|
||||
try {
|
||||
setRemoveError(null);
|
||||
await window.metona?.mcp?.removeServer(confirmRemove);
|
||||
setConfirmRemove(null);
|
||||
loadServers();
|
||||
} catch (err) {
|
||||
setRemoveError((err as Error).message ?? '移除失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>MCP 服务</Typography>
|
||||
@@ -598,10 +638,33 @@ function MCPSettings() {
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={0.5}>
|
||||
<Button size="small" sx={{ fontSize: 10, minWidth: 40 }} onClick={async () => { await window.metona?.mcp?.toggleServer(s.name, s.status !== 'connected'); loadServers(); }}>{s.status === 'connected' ? '断开' : '连接'}</Button>
|
||||
<Button size="small" color="error" sx={{ fontSize: 10, minWidth: 40 }} onClick={async () => { if (confirm(`确定移除 "${s.name}"?`)) { await window.metona?.mcp?.removeServer(s.name); loadServers(); } }}>移除</Button>
|
||||
{/* L-11 修复: 点击移除打开 MUI Dialog 二次确认,而非原生 confirm() */}
|
||||
<Button size="small" color="error" sx={{ fontSize: 10, minWidth: 40 }} onClick={() => setConfirmRemove(s.name)}>移除</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{/* L-11 修复: MUI Dialog 替代原生 confirm() */}
|
||||
<Dialog
|
||||
open={confirmRemove !== null}
|
||||
onClose={() => { setConfirmRemove(null); setRemoveError(null); }}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>确认移除</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2">
|
||||
确定移除 MCP 服务 "{confirmRemove}"?此操作不可撤销。
|
||||
</Typography>
|
||||
{removeError && (
|
||||
<Alert severity="error" sx={{ mt: 1, fontSize: 12 }}>移除失败:{removeError}</Alert>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => { setConfirmRemove(null); setRemoveError(null); }} color="inherit">取消</Button>
|
||||
<Button onClick={handleConfirmRemove} color="error" variant="contained">移除</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
{showAdd ? (
|
||||
<Stack spacing={1} sx={{ p: 1.5, borderRadius: 1.5, bgcolor: 'secondary.main', border: '1px solid', borderColor: 'divider' }}>
|
||||
<TextField size="small" value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="服务名称" />
|
||||
@@ -820,8 +883,33 @@ function AppearanceSettings({ theme, setTheme }: { theme: ThemeMode; setTheme: (
|
||||
function LogsSettings() {
|
||||
const [logLevel, setLogLevel] = useConfig('logging.level', 'info');
|
||||
const [clearing, setClearing] = useState<string | null>(null);
|
||||
// L-11 修复(审计补充): 用 MUI Dialog 替换原生 confirm() 和 alert(),保持 UI 一致性
|
||||
const [confirmClear, setConfirmClear] = useState<'sessions' | 'memories' | 'auditLogs' | null>(null);
|
||||
const [resultAlert, setResultAlert] = useState<{ type: 'success' | 'error'; message: string } | null>(null);
|
||||
const handleExport = async () => { if (!window.metona?.data?.exportData) return; const r = await window.metona.data.exportData(); if (r.success && r.data) { const b = new Blob([JSON.stringify(r.data, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = `metona-export-${Date.now()}.json`; a.click(); } };
|
||||
const handleClear = async (type: 'sessions' | 'memories' | 'auditLogs') => { const labels: Record<string, string> = { sessions: '所有会话', memories: '所有记忆', auditLogs: '审计日志' }; if (!confirm(`确定清理${labels[type]}?`)) return; setClearing(type); try { let r; if (type === 'sessions') r = await window.metona?.data?.clearSessions(); else if (type === 'memories') r = await window.metona?.data?.clearMemories(); else r = await window.metona?.data?.clearAuditLogs(); if (r?.success) alert(`${labels[type]}已清理`); else alert(`失败: ${r?.error}`); } catch (e) { alert(`失败: ${(e as Error).message}`); } setClearing(null); };
|
||||
const labels: Record<string, string> = { sessions: '所有会话', memories: '所有记忆', auditLogs: '审计日志' };
|
||||
|
||||
// L-11 修复(审计补充): 清理数据改用 Dialog 确认 + Alert 反馈,不再用原生 confirm/alert
|
||||
const handleClearConfirm = async () => {
|
||||
if (!confirmClear) return;
|
||||
const type = confirmClear;
|
||||
setClearing(type);
|
||||
setConfirmClear(null);
|
||||
try {
|
||||
let r;
|
||||
if (type === 'sessions') r = await window.metona?.data?.clearSessions();
|
||||
else if (type === 'memories') r = await window.metona?.data?.clearMemories();
|
||||
else r = await window.metona?.data?.clearAuditLogs();
|
||||
if (r?.success) {
|
||||
setResultAlert({ type: 'success', message: `${labels[type]}已清理` });
|
||||
} else {
|
||||
setResultAlert({ type: 'error', message: `失败: ${r?.error ?? '未知错误'}` });
|
||||
}
|
||||
} catch (e) {
|
||||
setResultAlert({ type: 'error', message: `失败: ${(e as Error).message}` });
|
||||
}
|
||||
setClearing(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
@@ -834,9 +922,39 @@ function LogsSettings() {
|
||||
<Divider />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: 'text.secondary' }}>数据管理</Typography>
|
||||
<Button variant="outlined" fullWidth size="small" onClick={handleExport}>📦 导出全部数据(JSON)</Button>
|
||||
<Button variant="outlined" fullWidth size="small" color="error" onClick={() => handleClear('sessions')} disabled={clearing !== null}>{clearing === 'sessions' ? '清理中...' : '🗑️ 清理所有会话'}</Button>
|
||||
<Button variant="outlined" fullWidth size="small" color="error" onClick={() => handleClear('memories')} disabled={clearing !== null}>{clearing === 'memories' ? '清理中...' : '🗑️ 清理所有记忆'}</Button>
|
||||
<Button variant="outlined" fullWidth size="small" color="error" onClick={() => handleClear('auditLogs')} disabled={clearing !== null}>{clearing === 'auditLogs' ? '清理中...' : '🗑️ 清理审计日志'}</Button>
|
||||
<Button variant="outlined" fullWidth size="small" color="error" onClick={() => { setResultAlert(null); setConfirmClear('sessions'); }} disabled={clearing !== null}>{clearing === 'sessions' ? '清理中...' : '🗑️ 清理所有会话'}</Button>
|
||||
<Button variant="outlined" fullWidth size="small" color="error" onClick={() => { setResultAlert(null); setConfirmClear('memories'); }} disabled={clearing !== null}>{clearing === 'memories' ? '清理中...' : '🗑️ 清理所有记忆'}</Button>
|
||||
<Button variant="outlined" fullWidth size="small" color="error" onClick={() => { setResultAlert(null); setConfirmClear('auditLogs'); }} disabled={clearing !== null}>{clearing === 'auditLogs' ? '清理中...' : '🗑️ 清理审计日志'}</Button>
|
||||
|
||||
{/* L-11 修复(审计补充): 清理数据确认 Dialog(替代原生 confirm()) */}
|
||||
<Dialog
|
||||
open={confirmClear !== null}
|
||||
onClose={() => setConfirmClear(null)}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>确认清理</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2">
|
||||
确定清理{confirmClear ? labels[confirmClear] : ''}?此操作不可撤销。
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmClear(null)} color="inherit">取消</Button>
|
||||
<Button onClick={handleClearConfirm} color="error" variant="contained">清理</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* L-11 修复(审计补充): 清理结果反馈 Alert(替代原生 alert()),3 秒后自动消失 */}
|
||||
{resultAlert && (
|
||||
<Alert
|
||||
severity={resultAlert.type}
|
||||
onClose={() => setResultAlert(null)}
|
||||
sx={{ fontSize: 12 }}
|
||||
>
|
||||
{resultAlert.message}
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user