Files
llm-in-text/FIX_LIST_ANONYMOUS_PRODUCTION.md

18 KiB
Raw Permalink Blame History

llm-in-text 修复清单(匿名可用版)

说明

这不是审计报告。

这份文档只回答三件事:

  1. 现在具体哪里有问题
  2. 问题为什么会发生
  3. 应该怎么改

前提按你的要求处理:

  • 网站是匿名可用的
  • 不做用户登录
  • 不做用户身份体系
  • 但仍然要防止接口被滥用、站点被刷爆、服务被恶意调用

匿名可用不等于完全不做保护。

对于这种网站,正确做法通常是:

  • 不做用户登录
  • 不在前端放任何真正的服务端秘密
  • 用服务端限流、来源限制、请求大小限制、网关策略保护接口
  • 必要时用站点级防刷手段,而不是用户级登录

1. 前端硬编码了服务端 API Key

具体问题

代码里把:

const API_KEY = 'your-secret-key-here'

直接写进了前端源码。

错误原因

前端代码最终会发到浏览器里。

只要用户能打开网站,就一定能在浏览器开发者工具、打包产物、网络请求里看到这个 key。
所以前端里的“密钥”根本不是密钥,只是公开字符串。

会导致什么

  • 任何人都可以绕过你的网站,直接写脚本刷你的后端
  • 这个 key 一旦被复制,就等于后端公开可调用

整改方式

你的场景不做登录,所以最简单、正确的做法是:

  1. 删除前端里的 API_KEY
  2. 后端不要再要求前端传固定共享 key
  3. 改成下面这套匿名保护方案:
    • 只允许来自你站点域名的浏览器请求
    • 网关层限流
    • 接口级限流
    • 请求体大小限制
    • 必要时加站点级验证码或 challenge而不是登录

你应该改成什么

  • src/utils/api.js 不再发 X-API-Key
  • src/utils/convert.js 不再发 X-API-Key
  • backend/main.py 删除固定 API_KEY 和对应校验逻辑

验收标准

  • 全仓库搜不到 your-secret-key-here
  • 前端请求头中不再包含固定共享 key

2. 后端 CORS 过宽

具体问题

现在配置是:

  • allow_origins=["*"]
  • allow_credentials=True
  • allow_methods=["*"]
  • allow_headers=["*"...]

错误原因

这是开发期常见的“先全开让它跑起来”的写法。
但生产里这样做会让任何站点都能更容易发起跨域调用。

会导致什么

  • 其他网站更容易借你的浏览器接口能力
  • 以后一旦加 cookie、session、任何凭据会立刻放大风险

整改方式

既然你是匿名站点,不做登录,那就更应该把跨域收紧:

  1. 只允许你的正式域名和本地开发域名
  2. 不要开 allow_credentials=True,匿名站一般不需要
  3. 只开放需要的方法和头

建议改法

把:

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并发送给后端

具体问题

流程是:

  1. 前端默认 privacyMode = false
  2. 前端请求 https://api.ipify.org?format=json
  3. 获取公网 IP
  4. 放进 X-Client-IP
  5. 后端再做地理位置推断

错误原因

这是把“个性化上下文”做成了默认行为。
但对匿名站点来说,这不是必要信息。

会导致什么

  • 页面会额外访问第三方服务
  • 用户 IP 会进入你的请求链路
  • 模型上下文中会混入地理位置信息

整改方式

如果你不需要真正的按地理位置个性化,就最简单:

  1. 删除 getClientIP()
  2. 删除调用 api.ipify.org
  3. 删除 X-Client-IP
  4. 后端删除 GeoIP 逻辑
  5. privacyMode 可以保留,但默认应是更安全的行为

你应该删什么

  • src/utils/api.js 中的 getClientIP
  • headers['X-Client-IP'] = clientIP
  • backend/main.pyget_client_ip
  • location = 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)

错误原因

这是开发期为了调试方便常见的写法。
但线上不应该把真实异常直接发给浏览器。

会导致什么

  • 暴露内部实现细节
  • 暴露依赖报错、路径、上游信息

整改方式

统一改成:

  1. 前端只收到固定错误码和通用提示
  2. 后端日志里保留详细异常
  3. 返回 request id 方便排查

建议响应格式

{
  "error": {
    "code": "UPSTREAM_TIMEOUT",
    "message": "Service temporarily unavailable",
    "request_id": "xxxx"
  }
}

验收标准

  • 前端不再收到 Python 原始报错
  • 日志可通过 request id 查到真实错误

5. /v1/convert/v1/ocr 没有文件安全边界

具体问题

现在的问题是:

  • 直接 base64 解码
  • 没有严格文件大小限制
  • 没有严格文件类型白名单
  • 没有魔数校验
  • 没有超时和并发保护

错误原因

当前实现是功能优先,默认相信前端传来的内容。
但上传链路是最容易出问题的地方之一。

会导致什么

  • 大文件压垮内存
  • 恶意文件拖慢 CPU
  • 第三方库处理异常文件时出故障

整改方式

ocr

  1. 图片大小上限先改为 5MB 或 10MB
  2. 只允许 jpg/png/webp
  3. 服务端校验 MIME 和魔数
  4. 增加请求超时
  5. 增加并发限制

convert

  1. 只允许明确白名单格式
  2. 每种格式单独设大小上限
  3. 服务端检查扩展名和文件头
  4. markitdown 执行增加超时
  5. 临时文件放到独立目录
  6. 临时文件异常时也要清理

建议白名单

  • .pdf
  • .docx
  • .pptx
  • .xlsx
  • .md
  • .txt

建议直接拒绝

  • 可执行文件
  • 压缩包
  • 未知二进制
  • 超大图片

验收标准

  • 超限文件返回 413
  • 非法类型返回 415
  • OCR/convert 高并发下不会拖垮服务

6. 没有限流,匿名站点很容易被刷

具体问题

  • 当前代码里没有 rate limit
  • 没有按 IP、UA、路径、时间窗做限制
  • ACTIVE_COMPLETIONS 只处理取消,不是限流器。backend/main.py:29

错误原因

因为现在的代码默认是“正常用户正常使用”。
但匿名公网站点上线后,必须假设会被脚本反复调用。

会导致什么

  • 模型成本失控
  • CPU、内存、连接数被耗尽
  • 服务变慢甚至不可用

整改方式

匿名站点不做登录,标准保护方式是限流:

  1. 反向代理层限流
  2. 应用层限流
  3. 高成本接口单独限流

建议策略

/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 调用没有明显的统一超时和失败分类。

错误原因

开发阶段一般默认上游会正常返回。
但生产里,上游模型服务经常会出现:

  • 变慢
  • 卡住
  • 连接失败
  • 超时

会导致什么

  • 请求挂很久
  • 连接被占住
  • 用户看起来像页面没响应

整改方式

  1. completions 设置明确超时,例如 15 到 30 秒
  2. OCR 设置更短或更明确的处理时限
  3. convert 设置文件转换超时
  4. 把错误分成:
    • timeout
    • unavailable
    • bad response
  5. 前端对这些错误做不同提示

验收标准

  • 上游模型挂掉时,接口会快速失败而不是一直卡住

8. 缺少健康检查接口

具体问题

当前没有明确的:

  • /health/live
  • /health/ready

错误原因

项目还是开发态,没有进入正式部署思路。

会导致什么

  • 你很难判断服务是否真的可用
  • 容器/进程平台无法正确探活

整改方式

增加两个接口:

/health/live

只表示“应用进程活着”

/health/ready

表示“应用准备好服务请求”

这个接口至少检查:

  • 模型上游是否可连接
  • 关键配置是否存在

验收标准

  • 反向代理或容器平台能用它判断是否接流量

9. 预览组件有 XSS 风险

具体问题

现在做法是:

  • v-html
  • html: true

错误原因

这意味着 markdown 里的原始 HTML 会被直接渲染。
如果内容来源不完全可信,这就是典型 XSS 入口。

会导致什么

  • 恶意脚本执行
  • 页面被注入恶意 DOM

整改方式

你有两个选择:

方案 A最简单直接关掉 HTML

把:

html: true

改成:

html: false

方案 B保留 HTML但做净化

  1. 引入 DOMPurify
  2. md.render() 后先 sanitize
  3. 再给 v-html

推荐

如果你不是必须支持原始 HTML直接用方案 A。

验收标准

  • 恶意 markdown/HTML 不会执行脚本

10. 前端默认配置会误连固定服务地址

具体问题

默认值里有固定公网域名和固定内网 IP。

错误原因

这是把“某次部署环境”写成了“代码默认值”。

会导致什么

  • 本地开发可能误连生产或旧环境
  • 新服务器部署时容易配错

整改方式

  1. 默认值改成本地开发地址或空值
  2. 关键配置不存在时直接报错
  3. .env.example 只放模板,不放真实地址

建议

  • VITE_API_BASE_URL 默认走同域,如 ''
  • 前端优先使用 /v1/... 反代
  • 后端 OLLAMA_HOST 必须来自环境变量

验收标准

  • 不配置环境变量时,不会误连旧服务

11. 包版本和界面版本不一致

具体问题

错误原因

一个是包元数据,一个是手写展示文案,没人保证同步。

会导致什么

  • 发布后你都不确定线上到底是哪版

整改方式

  1. 统一从 package.json 注入版本
  2. 前端不要手写版本号

验收标准

  • 页面显示版本和构建版本完全一致

12. package.json 缺少质量脚本

具体问题

当前只有:

  • dev
  • build
  • preview

没有:

  • test
  • lint
  • check

错误原因

项目还停留在“能运行”的阶段,没有建立质量门禁。

会导致什么

  • 任何改动都只能靠手工试
  • 回归问题容易漏

整改方式

至少补这些脚本:

"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也至少先把 testcheck 建起来。

验收标准

  • 以后每次改代码前后都能统一执行 check

13. 测试覆盖不够,缺关键路径

具体问题

当前测试主要是:

  • prompt 构造
  • 取消逻辑
  • LLM 消息结构

缺少:

  • 匿名访问基本流程
  • 错误响应格式
  • OCR 文件限制
  • convert 文件限制
  • 限流
  • XSS

错误原因

现有测试更偏功能开发时的局部验证,不是上线前测试矩阵。

整改方式

补这些测试:

  1. completions 正常返回
  2. completions 上游超时
  3. completions 请求超长
  4. OCR 非法类型
  5. OCR 超大图片
  6. convert 非法类型
  7. convert 超大文件
  8. 限流命中
  9. 未授权方案移除后,匿名访问可正常工作
  10. markdown 预览 XSS 样例

验收标准

  • 关键错误分支都有自动化测试

14. Service Worker 缓存策略还不够稳

具体问题

当前是手写缓存逻辑,版本固定写死。

错误原因

这是一个能用的基础实现,但不适合长期生产维护。

会导致什么

  • 更新后可能缓存混乱
  • 老版本资源残留

整改方式

如果你不强依赖离线能力:

  1. 先临时关闭 SW
  2. 等核心功能稳定后再重做 PWA

如果要保留:

  1. 用成熟方案接管,如 Vite PWA / Workbox
  2. 资源按 hash 控制
  3. 做更新提示

推荐

如果现在重点是先上线稳定版,先停掉 SW 更省事。

验收标准

  • 用户刷新后不会出现随机旧资源

15. 构建体积偏大

具体问题

本次构建已经出现大 chunk 警告,尤其是 Mermaid 相关包比较重。

错误原因

图表、编辑器、语法高亮、数学渲染这类库本身就大。
现在又没有足够按功能懒加载。

会导致什么

  • 首屏慢
  • 弱网体验差

整改方式

  1. Mermaid 按需加载
  2. 预览按需加载
  3. OCR / convert 相关 UI 按需加载
  4. 收敛 manualChunks

验收标准

  • 首页首次加载明显更轻

16. 匿名站点应该怎么做保护,而不是登录

这是你这个项目最关键的方向问题。

你不想做用户级网站,这完全可以。

那就按匿名站点的标准做:

必做

  1. 去掉前端共享密钥
  2. 收紧 CORS
  3. 加 Nginx / Cloudflare / 网关限流
  4. 应用层再做限流
  5. 限制请求体大小
  6. 限制 OCR/convert 并发
  7. 错误信息脱敏
  8. 加健康检查
  9. 处理 XSS

可选

  1. Cloudflare Turnstile
  2. 简单的人机验证 challenge
  3. 对高频匿名流量启用冷却时间

不必做

  1. 登录
  2. 注册
  3. 用户系统
  4. JWT

只要你的目标是匿名工具站,而不是多租户平台,上面这套就够了。


最简修复顺序

如果你要最低成本把项目拉到“能较安全公开上线”的程度,建议顺序是:

  1. 删除前端 API key 和后端固定 key 校验
  2. 删除 IP 获取和地理位置推断
  3. 收紧 CORS
  4. 统一错误响应
  5. 给 OCR/convert 加大小、类型、超时限制
  6. 加限流
  7. 修掉 MarkdownPreview 的 XSS 风险
  8. 增加 /health/live/health/ready
  9. test / check 脚本
  10. 视情况先关闭 service worker

这份清单对应的文件