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:
+4
-7
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user