fix: 审计5个FINDING修复 (files迭代)

- H-15 [MEDIUM] download端点加100MB大小限制
- H-02 [LOW] Content-Disposition filename sanitize(替换特殊字符)
- H-12 [LOW] FileDownload/FileRead/FileWrite path加max_length=4096
- H-27 [LOW] doPreview保存失败解析错误详情显示detail
- H-29 [INFO] esc()函数加注释说明用途
- H-01 [HIGH] 远程路径限制 — ACCEPTED_RISK(用户明确决策)
This commit is contained in:
Your Name
2026-05-30 23:44:35 +08:00
parent ac38442383
commit 9f7be66356
3 changed files with 13 additions and 5 deletions
+3 -3
View File
@@ -195,20 +195,20 @@ class FileOperation(BaseModel):
class FileDownload(BaseModel):
"""Download a file from a remote server."""
server_id: int = Field(..., ge=1)
path: str = Field(..., min_length=1)
path: str = Field(..., min_length=1, max_length=4096)
class FileRead(BaseModel):
"""Read text file content from a remote server."""
server_id: int = Field(..., ge=1)
path: str = Field(..., min_length=1)
path: str = Field(..., min_length=1, max_length=4096)
max_size: int = Field(1_000_000, ge=1, le=5_000_000) # 1MB default, 5MB max
class FileWrite(BaseModel):
"""Write text content to a file on a remote server."""
server_id: int = Field(..., ge=1)
path: str = Field(..., min_length=1)
path: str = Field(..., min_length=1, max_length=4096)
content: str = Field(..., max_length=5_000_000)
+8 -1
View File
@@ -1090,7 +1090,14 @@ async def download_file(
if stat.type not in ("regular file", "unknown"):
raise HTTPException(status_code=400, detail="只能下载文件,不能下载目录")
filename = os.path.basename(payload.path)
# H-15: Download file size limit (100MB)
MAX_DOWNLOAD_SIZE = 100 * 1024 * 1024
if hasattr(stat, "size") and stat.size and stat.size > MAX_DOWNLOAD_SIZE:
raise HTTPException(status_code=413, detail=f"文件大小 {stat.size} 字节超过下载限制 100MB")
# H-02: Sanitize filename for Content-Disposition (RFC 6266)
import re as _re
filename = _re.sub(r'[^\w.\-]', '_', os.path.basename(payload.path)) or "download"
async def file_generator():
async with sftp.open(payload.path, "rb") as f:
+2 -1
View File
@@ -114,6 +114,7 @@
else if(action==='select-all')toggleSelectAll(btn);
});
/** 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}
// ── Modal system (P2-9) ──
@@ -375,7 +376,7 @@
onConfirm:async(content)=>{
if(content===d.content){return}
const wr=await apiFetch(API+'/sync/write-file',{method:'POST',headers:apiHeadersJSON(),body:JSON.stringify({server_id:_currentServerId,path,content})});
if(wr&&wr.ok){toast('success','文件已保存')}else{toast('error','保存失败')}
if(wr&&wr.ok){toast('success','文件已保存')}else{const e=wr?await wr.json().catch(()=>({})):{};toast('error','保存失败: '+(e.detail||''))}
}
});
}