26.9(安全审计修复版)

Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务:

- 安全审计全部修复(docs/security-audit-2026-09-05.md):
  bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、
  全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、
  管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、
  会话默认 7 天、限流缓存故障降级、robots.txt 端点等
- 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固
- Redis 支持库号(FCB_REDIS_DB / redis://…/db URL)
- 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、
  提码 5 位起、chunk 32MiB 上限、admin 审计动作等)

验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
This commit is contained in:
2026-09-05 04:22:41 +08:00
commit 7f060dd0e4
173 changed files with 32455 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
# 环境变量与配置项
配置分三层:**默认值 → `FCB_*` 环境变量 → 数据库 settings KV(管理端运行时修改)**。
v2 起进程必需的环境变量为空集:数据库默认 **SQLite**modernc.org/sqlite 纯 Go 驱动,零外部依赖,
DSN 缺省落 `./data/filecodebox.db`);`FCB_DB_DRIVER=postgres``FCB_DB_DSN` 必需(需求 ⑧)。
## 环境变量(进程级)
| 变量 | 必需 | 默认 | 说明 |
|---|---|---|---|
| `FCB_DB_DRIVER` | ❌ | `sqlite` | 数据库驱动:`sqlite` / `postgres`(需求 ⑧) |
| `FCB_DB_DSN` | 视驱动 | `./data/filecodebox.db` | postgres:连接串(**必需**,如 `postgres://user:pass@host:5432/filecodebox?sslmode=disable`);sqlite:数据库文件路径(可空,父目录自动创建) |
| `FCB_REDIS_ADDR` | ❌ | 空 | Redis 地址(如 `redis:6379`),也支持 `redis://[:password@]host:port[/db]` / `rediss://`(TLS)URL 形式;**空则缓存降级为进程内存实现**(缓存故障时自动降级为进程内限流计数) |
| `FCB_REDIS_DB` | ❌ | `0` | Redis 逻辑库号 0-15;URL 形式地址显式携带 `/N` 时以 URL 为准 |
| `FCB_ADMIN_PASSWORD` | ❌ | 空 | 设置后服务首次启动即自动初始化管理员(≥8 位,不足告警跳过),消除 `/setup` 被抢占窗口;初始化完成后建议移除 |
| `FCB_LISTEN` | ❌ | `:8466` | HTTP 监听地址 |
| `FCB_STORAGE_ENGINE` | ❌ | `local` | 存储引擎:`local` / `s3` / `webdav` |
| `FCB_TRUSTED_PROXIES` | ❌ | 空 | 可信代理 CIDR(逗号分隔),命中时从 `X-Forwarded-For` 解析真实客户端 IP |
- SQLite 连接参数(驱动自动注入):`busy_timeout=10s` + `WAL` 日志模式 + `foreign_keys=1`;连接池 8/4。
- Postgres 连接池沿用 v1 参数(32/8,1h 轮换);`FCB_DB_DRIVER=postgres` 且未设 `FCB_DB_DSN` 时**启动直接报错**。
引擎相关环境变量(种子注入 settings KV,见《存储引擎配置》):`FCB_LOCAL_STORAGE_PATH`
`FCB_STORAGE_PATH``FCB_S3_ACCESS_KEY_ID``FCB_S3_SECRET_ACCESS_KEY``FCB_AWS_SESSION_TOKEN`
`FCB_S3_BUCKET_NAME``FCB_S3_ENDPOINT_URL``FCB_S3_REGION_NAME``FCB_S3_ADDRESSING_STYLE`
`FCB_WEBDAV_URL``FCB_WEBDAV_USERNAME``FCB_WEBDAV_PASSWORD``FCB_WEBDAV_ROOT_PATH`
部署用编排变量(`deploy/.env.example`):`WEB_PORT`(默认 8466)、
`POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB`(默认 filecodebox,仅 `--profile postgres` 时使用)。
## 配置项(settings KV,默认值对齐参考实现)
> 键名/类型/默认值/边界以 `server/internal/config/schema.go` 的 `KVSchema()` 为单一事实来源
> schema 同步测试保证与 defaults() 逐键一致);v2 新增键统一 snake_case。
### 站点信息与展示(需求 ①②③)
| 键 | 类型/边界 | 默认 | 说明 |
|---|---|---|---|
| `site_name` / `name` | string | 文件快传 | 站点名称(`site_name` 优先) |
| `site_domain` | string,≤256 | 空 | **v3.1**:站点对外域名(`http(s)://host[:port]`,不带路径;裸主机自动补 `http://`)。配置后分享链接(结果卡/管理端复制)用该域名生成——内网部署也能把公网链接发出去;留空=用当前访问地址 |
| `description` | string | 开箱即用的文件快传系统 | 站点描述 |
| `page_explain` | string | (合规声明) | 页面说明文案 |
| `keywords` | string | 文件快传, 文件分享… | SEO 关键词 |
| `logo_url` | string | 空(前端回落本地打包 `/assets/logo-*.svg`,需求 ⑤) | 页面导航 Logo,管理端可设任意 URL |
| `favicon_url` | string | 空(前端回落本地打包 `/assets/favicon-*.png`,需求 ⑤) | favicon / 备用 Logo |
| `opacity` | float | 0.9 | 界面不透明度 |
| `background` | string | 空 | 背景图 URL(参考实现既有键,v1 兼容保留) |
| `background_url` | string,≤2048 字符 | 空 | **v2 需求 ①**:背景图 URL 或上传后地址(空=主题默认;取值时 legacy `background` 键兜底)。管理端保存时校验协议白名单:仅 `http(s)``data:image/*` 与站内相对路径(防 `javascript:` 注入,非法 400 |
| `footer_text` | string,≤2000 字符 | 空 | **v2 需求 ②**:页脚自定义内容(纯文本或受控 HTML 片段) |
| `footer_beian` | string,≤128 字符 | 空 | **v2 需求 ②**:备案号(如 `京ICP备2024xxxxxx号-1`),展示于页脚 |
| `notify_enabled` | int0/1 | 1 | **v2 需求 ③**:通知开关(1=前台右上角悬浮窗展示 / 0=关闭) |
| `notify_title` | string,≤128 字符 | 系统通知 | 通知标题 |
| `notify_content` | string,≤2000 字符 | 欢迎使用… | 通知正文(**服务端白名单净化**:仅保留纯文本与 `<a href>` 为 http(s)/站内相对/`#` 锚点的链接,其余标签与事件属性剥离,保存与读取双侧生效) |
| `showAdminAddr` | int(0/1 | 0 | 是否展示后台入口 |
| `robotsText` | string | `User-agent: *\nDisallow: /` | robots.txt 内容(由公开端点 `GET /robots.txt` 输出) |
### 保存策略(需求 ④,上传页动态读取并在范围内选择)
| 键 | 类型/边界 | 默认 | 说明 |
|---|---|---|---|
| `max_save_seconds` | int640~31536000 | 0 | 最长保存秒数上限(0=仅默认 7 天兜底;>0 时按时间过期超限 403「限制最长时间为 X,可换用其他方式」)。**v3**:管理界面以「小时/天」下拉单位编辑(≥1 天自动显示天),提交时前端换算为秒——canonical 单位保持秒,接口语义不变 |
| `max_save_count` | int0~100000 | 0 | **v2 新增**:单次分享最大可取(保存)次数上限(0=不限制;`expire_style=count``expire_value` 超上限时 403「限制次数最多为 N 次」) |
| `expireStyle` | []string | `["day","hour","minute","forever","count"]` | 允许的过期方式白名单(上传时不在白名单 400「过期时间类型错误」) |
### 存储策略(需求 ④⑩)
| 键 | 类型/边界 | 默认 | 说明 |
|---|---|---|---|
| `uploadSize` | int641024~10GiB | 1048576010MB) | 单文件大小上限(字节),参考实现语义;`max_file_size=0` 时作为生效上限 |
| `max_file_size` | int640~10GiB | 0 | **v2 新增**:存储策略-单文件上限(字节),0=回落 `uploadSize`;超出 403(文案 humanSize 自适应 B/KB/MB/GB)。**v3**:管理界面以「MB/GB」下拉单位编辑(≥1 GiB 自动显示 GB),提交时前端换算为字节 |
| `allowed_file_types` | []string | `["*"]` | 允许类型白名单(扩展名/MIME 通配,`*` 不限制;非白名单 403「不允许上传该类型文件」) |
| `storageLimit` | int64,≥0 | 0 | 站点总容量(字节),0=不限制(超限 507) |
| `openUpload` | int0/1) | 1 | 游客上传开关(0 时上传接口要求管理员令牌 403) |
| `enableChunk` | int0/1 | 0 | 启用分片上传 |
### 上传频率限制(需求 ④,既有键对齐参考 ip_limit["upload"]
| 键 | 类型/边界 | 默认 | 说明 |
|---|---|---|---|
| `uploadCount` / `uploadMinute` | int1~10000 / 1~1440 | 10 / 1 | 窗口内允许上传次数 / 窗口分钟(上传成功才计数,超限 423;管理端修改后运行时同步限流规则,立即生效) |
| `errorCount` / `errorMinute` | int | 10 / 1 | 取件错误(失败计数)+ metadata 每次计数 |
| `loginCount` / `loginMinute` | int | 5 / 15 | 登录失败计数 |
### 安全与会话
| 键 | 默认 | 说明 |
|---|---|---|
| `admin_token` | 空(未初始化) | 管理员密码哈希(`sha256$salt$hash`);GET 配置时屏蔽为空串 |
| `jwt_secret` | 空 | JWT 签名密钥(初始化/改密时自动生成轮换;不下发;`settings.SensitiveKeys` 双模式下一致屏蔽) |
| `adminSessionExpire` | 604800(7 天) | 管理员会话秒数(须 1~365 整天) |
### 存储引擎(v3 运行时可配 + 热切换)
**`storage_engine`**v3 新增键):string`local|s3|webdav`,默认空=回落启动值 `FCB_STORAGE_ENGINE`
运行时切换走 **`POST /admin/storage/switch`**(JWT 保护):构建新引擎 → 健康检查通过才生效;
失败返回 503「存储引擎切换失败,已保持原引擎: …」且不改 KV。成功后持久化 `storage_engine`,重启沿用。
`GET /api/v1/config` 公开下发 `storage_engine` 当前名(仅名称,任何引擎参数/凭据不下发)。
引擎参数键(管理端可改;保存后对应引擎实例缓存失效,下次切换/构建生效):
| 键 | 默认 |
|---|---|
| `file_storage` | `local` |
| `storage_path` | 空 |
| `local_storage_path` | 空(容器内由 `FCB_LOCAL_STORAGE_PATH=/app/data` 注入) |
| `s3_access_key_id` / `s3_secret_access_key` / `aws_session_token` | 空 |
| `s3_bucket_name` / `s3_endpoint_url` / `s3_hostname` | 空 |
| `s3_region_name` | `auto` |
| `s3_signature_version` | `s3v4` |
| `s3_addressing_style` | `auto` |
| `s3_proxy` | 0 |
| `webdav_url` / `webdav_username` / `webdav_password` | 空 |
| `webdav_root_path` | `filebox_storage` |
| `webdav_proxy` | 0 |
> 敏感键 `webdav_password` / `s3_secret_access_key` / `aws_session_token`v3 加入 `settings.SensitiveKeys`):
> 管理端 GET 返回掩码 `******`PATCH 时空串或 `******` 表示不修改。直接写库 settings KV 后重启同样生效。
## 策略动态生效机制(v2 需求 ④⑩)
上传页通过 `GET /api/v1/config``config` 字段读取**当前策略快照**并在范围内渲染选项;
上传链路(`/share/file``/chunk/*``/presign/*`**每次请求实时读取** settings KV 同一组值校验:
- 管理端改策略(`PATCH /admin/config/update`)→ 公开 config 即时反映 → 后续上传立即按新策略执行(含 403/400 拒绝与恢复放行)。
- 生效上限:`max_file_size > 0` 时为 `max_file_size`,否则回落 `uploadSize`
- 校验点覆盖:单文件(`/share/file`)、分片 init 按分片数上限、分片上传累计、分片 complete 累计、预签名 init 声明大小,五处口径一致(`api.UploadPolicy.CheckSize`)。
## 公共配置接口
前端启动时经 `GET /api/v1/config` 获取站点公开配置(无需认证;v2 扩展需求 ①②③④⑩):
```json
{
"code": 200, "msg": "ok",
"data": {
"config": {
"name": "文件快传",
"description": "开箱即用的文件快传系统",
"explain": "请勿上传或分享违法内容…",
"logo_url": "",
"favicon_url": "",
"background_url": "",
"footer_text": "自定义页脚内容",
"footer_beian": "京ICP备2024xxxxxx号-1",
"notify_enabled": 1,
"notify_title": "系统通知",
"notify_content": "欢迎使用文件快传…",
"uploadSize": 10485760,
"max_file_size": 10485760,
"maxFileSize": 10485760,
"allowedFileTypes": ["*"],
"expireStyle": ["day", "hour", "minute", "forever", "count"],
"max_save_seconds": 0,
"maxSaveSeconds": 0,
"max_save_count": 0,
"maxSaveCount": 0,
"uploadCount": 10,
"uploadMinute": 1,
"enableChunk": false,
"openUpload": true
},
"meta": {
"version": "2.5.6",
"features": { "chunkUpload": false, "guestUpload": true }
}
}
}
```
> 策略字段 snake_case 与 camelCase 双份下发(前端宽松解析);响应为白名单显式构造,
> 任何敏感键(`admin_token`/`jwt_secret`)均不会出现。
## 健康检查
```bash
curl -s http://localhost:8466/api/v1/health
```
```json
{
"code": 200, "msg": "ok",
"data": {
"status": "ok",
"version": "2.5.6",
"storage": "local",
"time": "2025-06-01T12:00:00+08:00"
}
}
```