主题
07 · 公网试用上线 — 安全加固清单
面向"把站开放给群里的人试用"这个场景。目标:别人能来试,但薅不到你的模型额度、也难以攻击你的服务器。
核心策略:只给账号,不给模型——服务器不配任何 LLM key,用户注册后在设置页填自己的 key,烧的是他自己的额度。你零成本、零额度风险。
引流走 QQ 群(见 README):试用地址 + 邀请码只在群内私发,公开文档不放服务器地址。本清单里的域名/IP 一律用占位符。
一、上线前必做(P0)
1. .env 配置
bash
# —— 强制自配模型:三家 key 全部留空 ——
# 留空后,用户没配自己的 key 调生成会得到 400「请到模型设置页配置」,
# 绝不会回落到你的 key(见 app/llm/factory.py:resolve_provider_config)。
DEEPSEEK_API_KEY=
OPENAI_API_KEY=
GEMINI_API_KEY=
# —— 认证硬化 ——
APP_ENV=prod # 弱 JWT 密钥即拒启动(app/main.py)
JWT_SECRET=<64位随机串> # 见下方生成命令;⚠️ 一旦上线别再改(见第三节)
ADMIN_PASSWORD=<强密码> # 不改的话默认是 admin/admin12345,等于没锁
INVITE_CODE=<试用邀请码> # 或走多邀请码体系(后台管理,可设次数上限)生成随机密钥:
bash
python -c "import secrets; print(secrets.token_urlsafe(48))"2. 确认 admin 已改密
初始 admin 由启动迁移用 ADMIN_PASSWORD 创建。若你之前用默认密码起过服务,库里已有 admin/admin12345,改 .env 不会自动改旧密码——需登录后在后台改,或用新库。上线前务必确认 admin 不是默认密码。
3. 反向代理 + HTTPS + 防火墙
- 用 Caddy / Nginx 反代到容器的 8000 端口,配好 TLS(Caddy 自动签证书)。
- 防火墙只放 443(和限制来源 IP 的 22);关掉 8080/8000 的公网直连,别让人绕过反代直接打容器。
- 反代要设置可信的
X-Forwarded-For,限流才能按真实 IP 生效(见下)。
Caddy 示例:
your-domain.com {
reverse_proxy 127.0.0.1:8000
}4. 试用邀请码设次数上限
后台「邀请码管理」新建试用码时设 max_uses(如 50),用完自动失效——放量可控,不怕邀请码泄露后被无限刷号。
二、代码已内置的加固(无需额外操作)
| 能力 | 说明 | 位置 |
|---|---|---|
| 登录/注册限流 | 按 IP:登录 20 次/5min、注册 10 次/1h,超限 429 | app/ratelimit.py |
| per-user key 加密存储 | 用户 key 用 Fernet 加密后落库,防 DB 文件泄露连带泄 key | app/crypto.py |
| base_url SSRF 校验 | 保存配置时拒绝把 base_url 指向内网/本机/云元数据 | app/net_guard.py |
| 弱 JWT 密钥 fail-fast | APP_ENV=prod 下用默认密钥即拒启动 | app/main.py |
| 数据隔离 | 项目/任务按 user 隔离,越权按 404 处理 | app/auth.py |
| 密码 bcrypt 哈希 | 不存明文 | app/auth.py |
三、运维注意
- 别改已上线的
JWT_SECRET:key 加密的密钥从它派生,改了会导致①所有人 token 失效(需重登)②已存的模型 key 无法解密,用户得在设置页重填。真要轮换就提前通知。 - 多 worker 会削弱限流:当前 Dockerfile 是单进程 uvicorn,内存限流准确。若日后加
--workers或上 gunicorn,各进程计数独立,需换成 Redis 等共享存储。 - 定期备份 SQLite:
jarvis_write_data卷里的*.db;备份文件同样含(已加密的)用户 key,注意存放权限。 - 看谁在用:后台用户列表带用量汇总(
llm_usage表按 user 记账),能看出异常账号。
四、后续可选(P2)
- fail2ban / WAF 挡暴力与扫描。
- DNS rebinding 防护:SSRF 校验目前在"保存配置"时做,理论上可被解析时公网、请求时内网绕过;高价值场景可在发请求时再校验一次。
- 用户 key 加密密钥独立于 JWT_SECRET(单独
ENC_KEY),解耦"轮换登录密钥"和"重填模型 key"。