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