fix: 审计8个FINDING修复 (IDE编辑器+终端)

- H-01 [MEDIUM] esc() 加引号转义('"\') 防止onclick XSS
- H-02 [MEDIUM] initialPath 路径白名单校验(仅允许字母数字/.-_)
- H-03 [LOW] doNewFile 文件名白名单正则(替代简单的includes('/'))
- H-08 [LOW] Monaco CDN SRI — 已知限制, 版本锁定@0.45.0
- H-13 [MEDIUM] showModal 移除已删除的modalTextarea引用
- H-20 [LOW] initialPath仅首次连接cd, 不影响恢复的标签
- H-26 [MEDIUM] esc()跨文件一致性 — files.html对齐terminal.html
- H-30 [LOW] esc()注释更新
This commit is contained in:
Your Name
2026-05-31 02:06:42 +08:00
parent 34786737b4
commit e8c1acba9f
2 changed files with 11 additions and 10 deletions
+4 -7
View File
@@ -285,7 +285,7 @@
showModal({title:'📄 新建文件',input:true,inputDefault:'',confirmText:'创建',
onConfirm:async(name)=>{
if(!name)return;
if(name.includes('/')){toast('error','文件名能包含 /');return}
if(!/^[a-zA-Z0-9._\-]+$/.test(name)){toast('error','文件名能包含字母、数字、点、下划线、连字符');return}
const path=_currentPath.replace(/\/$/,'')+'/'+name;
const r=await apiFetch(API+'/sync/write-file',{method:'POST',headers:apiHeadersJSON(),body:JSON.stringify({server_id:_currentServerId,path,content:''})});
if(r&&r.ok){toast('success',`已创建: ${name}`);browseDir(_currentPath)}
@@ -315,8 +315,8 @@
let _filterExt='';
function filterByExt(ext){_filterExt=ext;renderFileList(_allEntries)}
/** H-29: HTML-escape a string for safe innerHTML insertion */
function esc(s){if(!s)return'';const d=document.createElement('div');d.textContent=s;return d.innerHTML}
/** HTML-escape for safe innerHTML + onclick attribute insertion */
function esc(s){if(!s)return'';const d=document.createElement('div');d.textContent=s;return d.innerHTML.replace(/"/g,'"').replace(/'/g,''')}
// ── Open SSH terminal to current server/path ──
function openTerminal(){
@@ -335,18 +335,15 @@
// ── Modal system (P2-9) ──
let _modalResolve=null;
function showModal({title,text,input,textarea,inputDefault,textareaValue,confirmText,danger,onConfirm}){
function showModal({title,text,input,inputDefault,confirmText,danger,onConfirm}){
document.getElementById('modalTitle').textContent=title;
const textEl=document.getElementById('modalText');
const inputEl=document.getElementById('modalInput');
const textareaEl=document.getElementById('modalTextarea');
const confirmBtn=document.getElementById('modalConfirm');
textEl.classList.toggle('hidden',!text);
textEl.textContent=text||'';
inputEl.classList.toggle('hidden',!input);
if(input){inputEl.value=inputDefault||'';setTimeout(()=>inputEl.focus(),50)}
textareaEl.classList.toggle('hidden',!textarea);
if(textarea){textareaEl.value=textareaValue||'';setTimeout(()=>textareaEl.focus(),50)}
confirmBtn.textContent=confirmText||'确认';
confirmBtn.className=`px-4 py-2 text-white text-sm rounded-lg transition ${danger?'bg-red-600 hover:bg-red-700':'bg-brand hover:bg-brand-dark'}`;
document.getElementById('modal').classList.remove('hidden');
+7 -3
View File
@@ -119,7 +119,10 @@ initLayout('servers');
// ── Parse params ──
const params = new URLSearchParams(window.location.search);
const initialServerId = parseInt(params.get('server_id'));
const initialPath = params.get('path') || ''; // Auto-cd after connect
const _rawPath = params.get('path') || '';
// H-02: Strict path whitelist — only allow safe characters
const initialPath = _rawPath && /^[a-zA-Z0-9/._\-]+$/.test(_rawPath) ? _rawPath : '';
let _initialCdSent = false; // H-20: Only cd on first connection, not restored tabs
if (!initialServerId) window.location.href = '/app/servers.html';
// ── localStorage keys ──
@@ -358,8 +361,9 @@ async function connectSession(idx) {
if (d && s.ws && s.ws.readyState === WebSocket.OPEN) {
s.ws.send(JSON.stringify({ type: 'RESIZE', cols: d.cols, rows: d.rows }));
}
// Auto-cd to initial path if specified in URL
if (initialPath && s.ws && s.ws.readyState === WebSocket.OPEN) {
// H-20: Auto-cd to initial path only on first connection (not restored tabs)
if (initialPath && !_initialCdSent && s.ws && s.ws.readyState === WebSocket.OPEN) {
_initialCdSent = true;
setTimeout(() => {
s.ws.send(JSON.stringify({ type: 'DATA', data: `cd ${initialPath}\r` }));
}, 200);