18 KiB
llm-in-text 修复清单(匿名可用版)
说明
这不是审计报告。
这份文档只回答三件事:
- 现在具体哪里有问题
- 问题为什么会发生
- 应该怎么改
前提按你的要求处理:
- 网站是匿名可用的
- 不做用户登录
- 不做用户身份体系
- 但仍然要防止接口被滥用、站点被刷爆、服务被恶意调用
匿名可用不等于完全不做保护。
对于这种网站,正确做法通常是:
- 不做用户登录
- 不在前端放任何真正的服务端秘密
- 用服务端限流、来源限制、请求大小限制、网关策略保护接口
- 必要时用站点级防刷手段,而不是用户级登录
1. 前端硬编码了服务端 API Key
具体问题
代码里把:
const API_KEY = 'your-secret-key-here'
直接写进了前端源码。
错误原因
前端代码最终会发到浏览器里。
只要用户能打开网站,就一定能在浏览器开发者工具、打包产物、网络请求里看到这个 key。
所以前端里的“密钥”根本不是密钥,只是公开字符串。
会导致什么
- 任何人都可以绕过你的网站,直接写脚本刷你的后端
- 这个 key 一旦被复制,就等于后端公开可调用
整改方式
你的场景不做登录,所以最简单、正确的做法是:
- 删除前端里的
API_KEY - 后端不要再要求前端传固定共享 key
- 改成下面这套匿名保护方案:
- 只允许来自你站点域名的浏览器请求
- 网关层限流
- 接口级限流
- 请求体大小限制
- 必要时加站点级验证码或 challenge,而不是登录
你应该改成什么
src/utils/api.js不再发X-API-Keysrc/utils/convert.js不再发X-API-Keybackend/main.py删除固定API_KEY和对应校验逻辑
验收标准
- 全仓库搜不到
your-secret-key-here - 前端请求头中不再包含固定共享 key
2. 后端 CORS 过宽
具体问题
现在配置是:
allow_origins=["*"]allow_credentials=Trueallow_methods=["*"]allow_headers=["*"...]
错误原因
这是开发期常见的“先全开让它跑起来”的写法。
但生产里这样做会让任何站点都能更容易发起跨域调用。
会导致什么
- 其他网站更容易借你的浏览器接口能力
- 以后一旦加 cookie、session、任何凭据,会立刻放大风险
整改方式
既然你是匿名站点,不做登录,那就更应该把跨域收紧:
- 只允许你的正式域名和本地开发域名
- 不要开
allow_credentials=True,匿名站一般不需要 - 只开放需要的方法和头
建议改法
把:
allow_origins=["*"]
allow_credentials=True
allow_methods=["*"]
allow_headers=["*", "X-API-Key", "X-Client-IP", "X-Request-Id"]
改成类似:
allow_origins=[
"https://your-domain.com",
"https://www.your-domain.com",
"http://localhost:5173",
]
allow_credentials=False
allow_methods=["POST", "OPTIONS"]
allow_headers=["Content-Type", "X-Request-Id"]
验收标准
- 非你自己域名的网页无法直接跨域调用你的接口
- 不再开放无用头和无用方法
3. 默认会去拿用户公网 IP,并发送给后端
具体问题
流程是:
- 前端默认
privacyMode = false - 前端请求
https://api.ipify.org?format=json - 获取公网 IP
- 放进
X-Client-IP - 后端再做地理位置推断
错误原因
这是把“个性化上下文”做成了默认行为。
但对匿名站点来说,这不是必要信息。
会导致什么
- 页面会额外访问第三方服务
- 用户 IP 会进入你的请求链路
- 模型上下文中会混入地理位置信息
整改方式
如果你不需要真正的按地理位置个性化,就最简单:
- 删除
getClientIP() - 删除调用
api.ipify.org - 删除
X-Client-IP - 后端删除 GeoIP 逻辑
privacyMode可以保留,但默认应是更安全的行为
你应该删什么
src/utils/api.js中的getClientIPheaders['X-Client-IP'] = clientIPbackend/main.py中get_client_iplocation = get_ip_location_text(client_ip)geoip.py如果以后不用可以移除
验收标准
- 前端网络面板中不再出现
api.ipify.org - 后端不再接收
X-Client-IP - prompt 不再包含用户位置
4. 后端把内部异常原样返回给前端
具体问题
现在写法是:
return JSONResponse(content={"error": str(e)}, status_code=500)
错误原因
这是开发期为了调试方便常见的写法。
但线上不应该把真实异常直接发给浏览器。
会导致什么
- 暴露内部实现细节
- 暴露依赖报错、路径、上游信息
整改方式
统一改成:
- 前端只收到固定错误码和通用提示
- 后端日志里保留详细异常
- 返回 request id 方便排查
建议响应格式
{
"error": {
"code": "UPSTREAM_TIMEOUT",
"message": "Service temporarily unavailable",
"request_id": "xxxx"
}
}
验收标准
- 前端不再收到 Python 原始报错
- 日志可通过 request id 查到真实错误
5. /v1/convert 和 /v1/ocr 没有文件安全边界
具体问题
- backend/main.py:239
- backend/main.py:268
- backend/main.py:275
- backend/main.py:281
- src/utils/convert.js:22
现在的问题是:
- 直接 base64 解码
- 没有严格文件大小限制
- 没有严格文件类型白名单
- 没有魔数校验
- 没有超时和并发保护
错误原因
当前实现是功能优先,默认相信前端传来的内容。
但上传链路是最容易出问题的地方之一。
会导致什么
- 大文件压垮内存
- 恶意文件拖慢 CPU
- 第三方库处理异常文件时出故障
整改方式
对 ocr
- 图片大小上限先改为 5MB 或 10MB
- 只允许
jpg/png/webp - 服务端校验 MIME 和魔数
- 增加请求超时
- 增加并发限制
对 convert
- 只允许明确白名单格式
- 每种格式单独设大小上限
- 服务端检查扩展名和文件头
markitdown执行增加超时- 临时文件放到独立目录
- 临时文件异常时也要清理
建议白名单
.pdf.docx.pptx.xlsx.md.txt
建议直接拒绝
- 可执行文件
- 压缩包
- 未知二进制
- 超大图片
验收标准
- 超限文件返回 413
- 非法类型返回 415
- OCR/convert 高并发下不会拖垮服务
6. 没有限流,匿名站点很容易被刷
具体问题
- 当前代码里没有 rate limit
- 没有按 IP、UA、路径、时间窗做限制
ACTIVE_COMPLETIONS只处理取消,不是限流器。backend/main.py:29
错误原因
因为现在的代码默认是“正常用户正常使用”。
但匿名公网站点上线后,必须假设会被脚本反复调用。
会导致什么
- 模型成本失控
- CPU、内存、连接数被耗尽
- 服务变慢甚至不可用
整改方式
匿名站点不做登录,标准保护方式是限流:
- 反向代理层限流
- 应用层限流
- 高成本接口单独限流
建议策略
/v1/completions
- 单 IP 每分钟 20 到 60 次
- 同时进行中的请求数限制 2 到 4 个
/v1/ocr
- 单 IP 每分钟 5 到 10 次
- 同时进行中的 OCR 限制更低
/v1/convert
- 单 IP 每分钟 3 到 5 次
- 强并发限制 1 到 2
还可以加什么
- Cloudflare Turnstile / hCaptcha 这类站点级防刷
- 对明显机器人流量加 challenge
这不需要登录,也不需要用户体系。
验收标准
- 连续脚本请求会命中 429
- 单个来源无法无限刷接口
7. 模型调用没有明确超时与失败策略
具体问题
当前 ollama.AsyncClient 调用没有明显的统一超时和失败分类。
错误原因
开发阶段一般默认上游会正常返回。
但生产里,上游模型服务经常会出现:
- 变慢
- 卡住
- 连接失败
- 超时
会导致什么
- 请求挂很久
- 连接被占住
- 用户看起来像页面没响应
整改方式
- completions 设置明确超时,例如 15 到 30 秒
- OCR 设置更短或更明确的处理时限
- convert 设置文件转换超时
- 把错误分成:
- timeout
- unavailable
- bad response
- 前端对这些错误做不同提示
验收标准
- 上游模型挂掉时,接口会快速失败而不是一直卡住
8. 缺少健康检查接口
具体问题
当前没有明确的:
/health/live/health/ready
错误原因
项目还是开发态,没有进入正式部署思路。
会导致什么
- 你很难判断服务是否真的可用
- 容器/进程平台无法正确探活
整改方式
增加两个接口:
/health/live
只表示“应用进程活着”
/health/ready
表示“应用准备好服务请求”
这个接口至少检查:
- 模型上游是否可连接
- 关键配置是否存在
验收标准
- 反向代理或容器平台能用它判断是否接流量
9. 预览组件有 XSS 风险
具体问题
现在做法是:
v-htmlhtml: true
错误原因
这意味着 markdown 里的原始 HTML 会被直接渲染。
如果内容来源不完全可信,这就是典型 XSS 入口。
会导致什么
- 恶意脚本执行
- 页面被注入恶意 DOM
整改方式
你有两个选择:
方案 A:最简单,直接关掉 HTML
把:
html: true
改成:
html: false
方案 B:保留 HTML,但做净化
- 引入 DOMPurify
md.render()后先 sanitize- 再给
v-html
推荐
如果你不是必须支持原始 HTML,直接用方案 A。
验收标准
- 恶意 markdown/HTML 不会执行脚本
10. 前端默认配置会误连固定服务地址
具体问题
默认值里有固定公网域名和固定内网 IP。
错误原因
这是把“某次部署环境”写成了“代码默认值”。
会导致什么
- 本地开发可能误连生产或旧环境
- 新服务器部署时容易配错
整改方式
- 默认值改成本地开发地址或空值
- 关键配置不存在时直接报错
.env.example只放模板,不放真实地址
建议
VITE_API_BASE_URL默认走同域,如''- 前端优先使用
/v1/...反代 - 后端
OLLAMA_HOST必须来自环境变量
验收标准
- 不配置环境变量时,不会误连旧服务
11. 包版本和界面版本不一致
具体问题
- package.json:4 是
0.0.0 - src/components/SettingsPanel.vue:275 写的是
v0.1.0-beta
错误原因
一个是包元数据,一个是手写展示文案,没人保证同步。
会导致什么
- 发布后你都不确定线上到底是哪版
整改方式
- 统一从
package.json注入版本 - 前端不要手写版本号
验收标准
- 页面显示版本和构建版本完全一致
12. package.json 缺少质量脚本
具体问题
当前只有:
devbuildpreview
没有:
testlintcheck
错误原因
项目还停留在“能运行”的阶段,没有建立质量门禁。
会导致什么
- 任何改动都只能靠手工试
- 回归问题容易漏
整改方式
至少补这些脚本:
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "pytest backend/tests -q",
"lint": "eslint src",
"check": "npm run lint && npm run build && pytest backend/tests -q"
}
如果前端暂时没配 ESLint,也至少先把 test 和 check 建起来。
验收标准
- 以后每次改代码前后都能统一执行
check
13. 测试覆盖不够,缺关键路径
具体问题
当前测试主要是:
- prompt 构造
- 取消逻辑
- LLM 消息结构
缺少:
- 匿名访问基本流程
- 错误响应格式
- OCR 文件限制
- convert 文件限制
- 限流
- XSS
错误原因
现有测试更偏功能开发时的局部验证,不是上线前测试矩阵。
整改方式
补这些测试:
- completions 正常返回
- completions 上游超时
- completions 请求超长
- OCR 非法类型
- OCR 超大图片
- convert 非法类型
- convert 超大文件
- 限流命中
- 未授权方案移除后,匿名访问可正常工作
- markdown 预览 XSS 样例
验收标准
- 关键错误分支都有自动化测试
14. Service Worker 缓存策略还不够稳
具体问题
当前是手写缓存逻辑,版本固定写死。
错误原因
这是一个能用的基础实现,但不适合长期生产维护。
会导致什么
- 更新后可能缓存混乱
- 老版本资源残留
整改方式
如果你不强依赖离线能力:
- 先临时关闭 SW
- 等核心功能稳定后再重做 PWA
如果要保留:
- 用成熟方案接管,如 Vite PWA / Workbox
- 资源按 hash 控制
- 做更新提示
推荐
如果现在重点是先上线稳定版,先停掉 SW 更省事。
验收标准
- 用户刷新后不会出现随机旧资源
15. 构建体积偏大
具体问题
本次构建已经出现大 chunk 警告,尤其是 Mermaid 相关包比较重。
错误原因
图表、编辑器、语法高亮、数学渲染这类库本身就大。
现在又没有足够按功能懒加载。
会导致什么
- 首屏慢
- 弱网体验差
整改方式
- Mermaid 按需加载
- 预览按需加载
- OCR / convert 相关 UI 按需加载
- 收敛
manualChunks
验收标准
- 首页首次加载明显更轻
16. 匿名站点应该怎么做保护,而不是登录
这是你这个项目最关键的方向问题。
你不想做用户级网站,这完全可以。
那就按匿名站点的标准做:
必做
- 去掉前端共享密钥
- 收紧 CORS
- 加 Nginx / Cloudflare / 网关限流
- 应用层再做限流
- 限制请求体大小
- 限制 OCR/convert 并发
- 错误信息脱敏
- 加健康检查
- 处理 XSS
可选
- Cloudflare Turnstile
- 简单的人机验证 challenge
- 对高频匿名流量启用冷却时间
不必做
- 登录
- 注册
- 用户系统
- JWT
只要你的目标是匿名工具站,而不是多租户平台,上面这套就够了。
最简修复顺序
如果你要最低成本把项目拉到“能较安全公开上线”的程度,建议顺序是:
- 删除前端 API key 和后端固定 key 校验
- 删除 IP 获取和地理位置推断
- 收紧 CORS
- 统一错误响应
- 给 OCR/convert 加大小、类型、超时限制
- 加限流
- 修掉
MarkdownPreview的 XSS 风险 - 增加
/health/live和/health/ready - 补
test/check脚本 - 视情况先关闭 service worker