feat(oss): 补齐开源声明体系(MIT + 第三方声明 + 贡献/安全/行为准则)

* LICENSE              —— MIT
  * THIRD-PARTY-NOTICES  —— 依赖清单、再分发合规说明(含随仓库分发的
                            Apache ECharts 5.6.0 / Apache-2.0)与自查清单
  * CONTRIBUTING.md      —— 开发环境、五层验证、必须遵守的不变量、提交规范
  * SECURITY.md          —— 漏洞私有报告渠道、已有措施、已知非目标
  * CODE_OF_CONDUCT.md   —— 改编自 Contributor Covenant 2.1
  * .github/             —— Bug 报告 / 功能建议表单 + PR 模板
  * .editorconfig        —— 与 .gitattributes 保持一致
  * 全部 Python / Shell 源文件加 SPDX-License-Identifier: MIT 头
  * README 增加「开源与许可」章节与许可标识
这个提交包含在:
2026-09-14 16:15:26 +08:00
父节点 9469a61bbc
当前提交 23799b4ea5
共修改 30 个文件,包含 671 行新增和 3 行删除
+33
查看文件
@@ -0,0 +1,33 @@
# 统一的编辑器约定。与 .gitattributes(* text=auto eol=lf)保持一致。
# 这样无论谁在什么系统上编辑,提交进来的都是 LF 与 UTF-8。
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{html,css,js,json,yml,yaml,svg}]
indent_size = 2
[*.py]
indent_size = 4
max_line_length = 100
[*.md]
# Markdown 里行尾两个空格是有意义的分行,别自动删掉
trim_trailing_whitespace = false
[*.sh]
indent_size = 4
# 容器 entrypoint 必须是 LF,否则报 exec format error / no such file or directory
end_of_line = lf
[Makefile]
indent_style = tab
[*.{png,jpg,jpeg,gif,ico,woff,woff2,sqlite}]
insert_final_newline = false
+83
查看文件
@@ -0,0 +1,83 @@
name: Bug 报告
description: 报告一个可复现的问题
title: "[Bug] "
labels: [bug]
body:
- type: markdown
attributes:
value: |
感谢反馈。提交前请先确认:
1. 你已经读过 [FAQ](https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/FAQ.md) 与 [部署排错](https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/DEPLOYMENT.md);
2. **不要贴真实 Cookie、secret_key 或真实用量数据**——需要复现请用 `python tools/demo_data.py` 生成的示例数据。
- type: input
id: version
attributes:
label: 版本 / 提交号
description: 例如 1.1.0,或 `git rev-parse --short HEAD` 的输出
placeholder: 1.1.0 / 118e27f
validations:
required: true
- type: dropdown
id: deploy
attributes:
label: 部署方式
options:
- Docker Compose(命名卷,推荐)
- Docker Compose + hostdir 叠加层(绑定挂载)
- 裸机 waitress
- 裸机 Flask 开发服务器
- 其他(请在补充说明里写)
validations:
required: true
- type: input
id: env
attributes:
label: 运行环境
description: 操作系统与 Python 版本
placeholder: Windows 11 + Docker Desktop 4.3x / Ubuntu 24.04 + Python 3.13
validations:
required: true
- type: textarea
id: what
attributes:
label: 现象
description: 发生了什么?预期是什么?
validations:
required: true
- type: textarea
id: repro
attributes:
label: 复现步骤
description: 越具体越好。涉及数据口径的问题请说明用的是示例数据还是真实数据。
placeholder: |
1. `docker compose up -d --build`
2. 打开 /dashboard
3. 点击「30 天」
4. 看到 …
validations:
required: true
- type: textarea
id: logs
attributes:
label: 日志 / 报错
description: |
相关日志。**请先脱敏**(抹掉 Cookie、域名、内网 IP、真实模型名)。
大屏类问题请附浏览器 Console 的报错。
render: text
validations:
required: false
- type: textarea
id: checks
attributes:
label: 已做过的自查
description: 例如是否跑过 `tools/check_live.py`、是否换过浏览器、是否重启过容器
validations:
required: false
+8
查看文件
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 使用问题 / 部署排错
url: https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/FAQ.md
about: 常见问题、Cookie 获取、时区与调度、备份恢复等,先看 FAQ 与部署文档
- name: 安全漏洞
url: https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/SECURITY.md
about: 请勿公开提交可直接利用的漏洞细节,按 SECURITY.md 走私有渠道
@@ -0,0 +1,47 @@
name: 功能建议
description: 提出一个新功能或改进
title: "[Feature] "
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
在提建议前,请先看一眼 [架构说明](https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/ARCHITECTURE.md)
里的「已知边界」——有些能力是**刻意不做**的(例如不引入 APScheduler、不新增第三方依赖)。
- type: textarea
id: problem
attributes:
label: 你想解决什么问题
description: 先说场景与痛点,再说方案。这样更容易判断有没有更简单的做法。
placeholder: 我在做 … 的时候,必须手动 … ,很费时。
validations:
required: true
- type: textarea
id: proposal
attributes:
label: 你期望的做法
description: 如果有具体的接口/页面/参数设计,写在这里
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 考虑过的替代方案
description: 以及为什么它们不够好
validations:
required: false
- type: checkboxes
id: constraints
attributes:
label: 约束自查
options:
- label: 该功能不需要新增第三方依赖(或已在下方说明理由)
required: false
- label: 该功能不破坏「SQLite 单写者」这一前提
required: false
- label: 若涉及列表类接口,我不会在其中返回 `prompt` 全文
required: false
+46
查看文件
@@ -0,0 +1,46 @@
## 这个 PR 做了什么
<!-- 一句话说清。若是修 bug,请写清根因,而不是只写「修了个 bug」。 -->
## 关联 Issue
<!-- 例如 Closes #12 -->
## 改动类型
- [ ] Bug 修复
- [ ] 新功能
- [ ] 重构(不改变外部行为)
- [ ] 文档
- [ ] 构建 / 部署 / CI
## 改动清单
<!-- 文件路径 + 做了什么,便于快速 review。例:
- `workbuddy_portal/query.py` —— bundle() 里 daily 改为全量下发
- `docs/API.md` —— 同步说明 daily 的范围语义
-->
## 验证情况
<!-- 请贴出实际输出,不要只写「已测试」。至少覆盖第 2 层。 -->
| 检查 | 结果 |
|---|---|
| `python -m compileall -q workbuddy_portal manage.py tools` | |
| `python tools/smoke.py` | `RESULT: ok=?? fail=0` |
| `python tools/check_live.py`(如起了服务) | `RESULT: ok=?? fail=0` |
| `python tools/shots.py`(如改了前端) | 无 JS 报错 |
| `docker compose up -d --build`(如改了容器相关) | 容器 healthy |
## 破坏性变更 / 需要部署方做的事
<!-- 有就写:环境变量、数据迁移、需要重建镜像、需要改反向代理配置…;没有就写「无」 -->
## 自查确认
- [ ] 没有提交任何凭据、`data/usage.sqlite`、`logs/*`、`instance.json`(已用 `git check-ignore -v` 复核过忽略规则)
- [ ] 没有在列表类接口里新增返回 `prompt` 全文
- [ ] 若涉及数据口径变更,我已用**独立聚合**与页面结果逐项比对
- [ ] 新增的第三方资源已登记到 `THIRD-PARTY-NOTICES.md`
- [ ] 文档(README / docs / CHANGELOG)已同步更新
+59
查看文件
@@ -0,0 +1,59 @@
# 行为准则(Code of Conduct)
## 我们的承诺
为了营造开放、友善的协作环境,我们作为贡献者与维护者承诺:**让每个人参与本项目的体验都不受骚扰**,
无论其年龄、体型、可见或不可见的残障、族裔、性别认同与表达、经验水平、教育程度、
社会经济状况、国籍、外貌、种族、宗教,或性取向与身份认同。
## 我们的标准
**有助于营造积极环境的行为:**
- 对他人展现同理心与善意
- 尊重不同的意见、观点与经验
- 给出并优雅地接受建设性的反馈
- 承担责任、向被我们影响的人致歉,并从中学习
- 关注对整个社区最有利的事,而不只是对我们个人
**不可接受的行为:**
- 性化的言语或图像,以及任何形式的性关注或挑逗
- 挑衅、侮辱或贬损性评论,以及人身攻击或政治攻击
- 公开或私下的骚扰
- 未经明确许可,公布他人的私人信息(如真实姓名、住址、邮箱、凭据等)
- 其他在专业场合中可被合理视为不当的行为
> 与本项目技术定位直接相关的一条:**请勿在 Issue、PR、讨论或截图中提交真实的账号凭据、
> 云端 Cookie、`secret_key` 或真实用户的用量数据**。需要复现时请用 `tools/demo_data.py`
> 生成的示例数据,或自行脱敏。这类内容会被立即删除。
## 维护者的责任
维护者负责澄清并执行上述标准,对任何被视为不当、威胁、冒犯或有害的行为,
有权采取适当且公平的纠正措施,包括删除、编辑或拒绝评论、提交、代码与 Issue,
并在必要时临时或永久禁止任何贡献者参与。
## 适用范围
本准则适用于所有项目空间,也适用于个人在**公开场合代表本项目**时的言行。
## 报告与执行
如遇滥用、骚扰或其他不可接受的行为,请通过维护者在代码托管平台上公布的联系方式私下报告
(参见 [SECURITY.md](SECURITY.md) 中的私有渠道)。所有投诉都会被及时、公正地审查与处理。
维护者有义务尊重报告者的隐私与安全。
## 执行准则
维护者将按下述梯度决定后果:
1. **更正** — 私下书面警告,说明违规性质并解释为何不当。必要时要求公开致歉。
2. **警告** — 在一段时间内禁止与相关人员互动;违反将导致临时或永久封禁。
3. **临时封禁** — 在指定期限内禁止任何形式的公开或私下互动。
4. **永久封禁** — 永久禁止在项目内进行任何形式的公开互动。
## 致谢
本准则改编自 [Contributor Covenant](https://www.contributor-covenant.org/) 2.1 版
(原文以 CC BY 4.0 发布)。执行准则参考其「Enforcement Guidelines」部分。
+112
查看文件
@@ -0,0 +1,112 @@
# 贡献指南(Contributing)
感谢你有兴趣改进 WorkBuddy Portal。这是一个**单进程 Flask + SQLite** 的轻量项目,
刻意保持了很小的依赖面与很平的目录结构——请先花两分钟读完本文,你的改动会更顺利被接受。
---
## 一、开发环境
| 项 | 要求 |
|---|---|
| Python | **3.13**(Docker 镜像用的就是 3.13-slim;3.11+ 一般也可) |
| 操作系统 | Linux / macOS / Windows 均可,但**注意**下面「Windows 特有坑」一节 |
| 依赖 | `pip install -r requirements.txt` |
| 可选 | Playwright + Chromium(只有跑界面截图才需要) |
起步:
```bash
git clone <你的仓库地址> workbuddy-portal
cd workbuddy-portal
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python manage.py init # 建表 + 建管理员(默认 admin/admin123)
python manage.py serve --port 8848 # 或 docker compose up -d --build
```
## 二、用示例数据开发,别用真实数据
`tools/demo_data.py` 会生成一份**完全合成**的数据集(假模型名、假 Prompt、偏斜的积分分布),
放在 `data/demo/` 下(该目录已在 `.gitignore` 内):
```bash
python tools/demo_data.py # 默认 data/demo,管理员 admin/admin123
WB_DATA_DIR=$PWD/data/demo python manage.py serve --port 8849 --no-scheduler
```
这样你既能有一个「看起来像真的」的界面来调试,也不会把任何真实用量带进仓库或截图。
## 三、改动前请先跑一遍验证
项目有一层层递进的验证,**代价从低到高**,改完至少跑到第 2 层:
| # | 命令 | 覆盖什么 | 需要什么 |
|---|---|---|---|
| 1 | `python -m compileall -q workbuddy_portal manage.py tools` | 语法 | — |
| 2 | `python tools/smoke.py` | **99 项**离线断言:全页面只读渲染、模板残留检测、历史缺陷防回归、静态资源、CSS 类名对账 | 无(用 Flask test_client,不启服务) |
| 3 | `python tools/check_live.py --base http://127.0.0.1:8848` | **56 项**真实 HTTP 断言,含登录/CSRF/开放重定向 | 一个运行中的服务 |
| 4 | `python tools/shots.py --base http://127.0.0.1:8849` | 登录后逐页截图并收集 `console`/`pageerror` | Playwright + Chromium |
| 5 | `docker compose up -d --build && docker compose ps` | 容器化路径 | Docker |
> `tools/shots.py` 是**最有价值的一层**:项目曾经出过「大屏整页全白」的 bug,
> 只有它抓到了(`smoke` 与 `check_live` 都放过了)。改前端务必跑。
这三支脚本都会打印 `RESULT: ok=N fail=0`,`fail` 不为 0 时退出码是 1,可直接接进 CI。
## 四、必须遵守的几条不变量
这些是踩过坑之后定下来的,破坏它们会在生产上以很隐蔽的方式出问题:
1. **SQLite 只允许一个写者。** 任何采集动作都要走 `collect._Lock()`(`data/collect.lock`)。
不要起多个带调度的进程;多实例部署时其余实例设 `WB_DISABLE_SCHEDULER=1`。
2. **列表类接口默认不返回 `prompt` 全文。** 它占原始体积约 80%。只有 `/api/top` 与
`/api/records` 带,且都做截断。新增接口请沿用这个约定。
3. **两种字段命名契约不要互相「统一」**:`/api/bundle` 用短键(`d/c/k/m/cl/t/px`,大屏页依赖),
`/api/records` 用可读全名。改错会让大屏静默渲染成空白。
4. **流式响应里不要复用 `db.get_db()`。** Flask 在响应迭代开始前就会关掉请求上下文里的连接,
生成器一读库就 `Cannot operate on a closed database`。要在生成器内部自建连接并 `finally` 关闭。
5. **Docker 部署用命名卷,不要退回绑定挂载。** Windows + Docker Desktop 走 9p,
宿主进程碰过 WAL 库之后容器会永久打不开数据库(详见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md))。
6. **`.gitignore` 不支持行尾注释**——规则后跟 `# 注释` 会让整行失效。注释必须单独占一行,
改完用 `git check-ignore -v <file>` 逐条确认命中。
## 五、代码风格
- 遵循 PEP 8;行宽 100。
- **注释与文档字符串用中文**,说明「为什么这么做」而不是「这行在做什么」。
- 提交前用 `python -m compileall` 与 `python tools/smoke.py` 自查。
- 不要引入新的第三方依赖,除非有充分理由并在 PR 里说明——这个项目的卖点之一就是依赖少。
如果确实新增了,请同步登记到 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。
## 六、提交与 PR
提交信息用 **Conventional Commits**,一句话说清「改了什么」即可,正文可写动机:
```
fix(docker): 数据改用 Docker 命名卷,修容器打不开数据库的问题
feat(api): 新增 /api/export 支持按筛选条件导出 CSV
docs: 补充反向代理部署示例
```
PR 请包含:
- **动机**:解决什么问题,或复现步骤
- **改动**:涉及哪些文件、有没有破坏性变更
- **验证**:贴出 `smoke` / `check_live` 的 `RESULT` 行;改前端再贴截图脚本的输出
- 若改动影响数据口径(聚合、去重、时区),请**额外写一份独立聚合与之比对**,
不要只靠肉眼看页面
## 七、Windows 特有坑(若你在 Windows 上开发)
- 宿主 Windows 进程访问过 `data/usage.sqlite` 后,**容器内**会打不开同一个库。
用命名卷部署时,宿主侧跑 CLI 请一律走 `docker compose exec portal python manage.py …`。
- Git 的 `/tmp` 会被解析成 `C:\tmp`,`git commit -F /tmp/msg.txt` 会失败——用仓库内路径。
- 本机若开着 HTTP 代理,`curl http://127.0.0.1:…` 会被代理拦成 502,探测本地服务要加 `--noproxy '*'`。
- 行尾:仓库用 `.gitattributes` 锁死 `eol=lf`,`docker/*.sh` 若带 CRLF 会在容器里报
`exec format error`。
---
有任何不确定的地方,先在 Issue 里问,比写完再返工更省事。
+21
查看文件
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Wang Chuanli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+46 -1
查看文件
@@ -14,9 +14,10 @@
| 部署 | Docker Compose / 裸机 `waitress`;镜像可推 Gitea 容器注册表 | | 部署 | Docker Compose / 裸机 `waitress`;镜像可推 Gitea 容器注册表 |
| 鉴权 | 全站登录 + CSRF + 角色(管理员 / 普通用户),凭证存库、页面只回掩码 | | 鉴权 | 全站登录 + CSRF + 角色(管理员 / 普通用户),凭证存库、页面只回掩码 |
| 版本 | v1.1.0 | | 版本 | v1.1.0 |
| **许可证** | **MIT**(第三方组件与再分发资源见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)) |
**目录**:[核心特性](#核心特性) · [架构](#架构一图) · [快速开始](#快速开始) · [命令一览](#命令一览) · **目录**:[核心特性](#核心特性) · [架构](#架构一图) · [快速开始](#快速开始) · [命令一览](#命令一览) ·
[页面一览](#页面一览) · [接口一览](#接口一览) · [文档导航](#文档导航) · [安全须知](#安全须知) [页面一览](#页面一览) · [接口一览](#接口一览) · [文档导航](#文档导航) · [安全须知](#安全须知) · [开源与许可](#开源与许可)
--- ---
@@ -245,6 +246,11 @@ workbuddy-portal/
| [docs/API.md](docs/API.md) | 开发 / 集成 | 接口参考:路径、参数、返回结构、错误码 | | [docs/API.md](docs/API.md) | 开发 / 集成 | 接口参考:路径、参数、返回结构、错误码 |
| [docs/FAQ.md](docs/FAQ.md) | 所有人 | 常见问题:采集为空、Cookie 失效、时区、性能、权限 | | [docs/FAQ.md](docs/FAQ.md) | 所有人 | 常见问题:采集为空、Cookie 失效、时区、性能、权限 |
| [docs/CHANGELOG.md](docs/CHANGELOG.md) | 所有人 | 变更日志 | | [docs/CHANGELOG.md](docs/CHANGELOG.md) | 所有人 | 变更日志 |
| [CONTRIBUTING.md](CONTRIBUTING.md) | 贡献者 | 贡献指南:开发环境、验证分层、必须遵守的不变量、提交规范 |
| [SECURITY.md](SECURITY.md) | 运维 / 安全 | 安全策略:漏洞私有报告渠道、已有措施、已知非目标 |
| [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) | 合规 | 第三方组件清单与许可证(含随仓库再分发的 ECharts) |
| [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | 所有人 | 行为准则 |
| [LICENSE](LICENSE) | 所有人 | MIT 许可证全文 |
--- ---
@@ -259,3 +265,42 @@ workbuddy-portal/
- **开放重定向防护**:登录跳转的 `next` 只接受站内相对路径,`//evil.com` 这类协议相对 URL 一律回落到 `/`。 - **开放重定向防护**:登录跳转的 `next` 只接受站内相对路径,`//evil.com` 这类协议相对 URL 一律回落到 `/`。
- **登录限速**:同 IP 连续失败 5 次锁定 10 分钟;失败计数表有上限与 TTL。 - **登录限速**:同 IP 连续失败 5 次锁定 10 分钟;失败计数表有上限与 TTL。
- **不进版本库的文件**:`data/instance.json`(含 `secret_key`)、`data/usage.sqlite`、`logs/`、`.env`(含明文密码)。 - **不进版本库的文件**:`data/instance.json`(含 `secret_key`)、`data/usage.sqlite`、`logs/`、`.env`(含明文密码)。
---
## 开源与许可
本项目以 **MIT 许可证**发布,全文见 [LICENSE](LICENSE)。你可以自由使用、修改、商用与再分发,
只需保留版权声明与许可声明。
### 第三方组件(务必看一眼)
用量大屏**随仓库再分发了 Apache ECharts 5.6.0**(`workbuddy_portal/web/static/dashboard/vendor/echarts.min.js`,
Apache-2.0 许可)——之所以内置而不走 CDN,是为了让大屏在局域网内离线可用。
按 Apache-2.0 第 4 条,再分发时需保留其许可证与版权声明(该文件头部已自带)。
其余运行期依赖(Flask / waitress / openpyxl)不在本仓库内,由使用方安装时获取。
完整的依赖清单、许可证对照表与**合规自查清单**见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。
### 参与贡献
- 想改代码?先读 [CONTRIBUTING.md](CONTRIBUTING.md) —— 里面有**必须遵守的几条不变量**
(SQLite 单写者、列表接口不回 `prompt` 全文、两种字段命名契约不要互相「统一」……),
以及从 `compileall` 到容器验证的五层自检该怎么跑。
- 有想法但手上没有真实数据?`python tools/demo_data.py` 会生成一份**完全合成**的示例库,
写到 `data/demo/`(已在 `.gitignore` 内),可直接拿来调试界面与截图。
- 发现安全漏洞?**请不要开公开 Issue**,按 [SECURITY.md](SECURITY.md) 走私有渠道。
- 参与本项目即表示你同意遵守 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)。
### 文档里的数据都是合成的
`docs/images/` 的全部界面截图与 `docs/` 中的 JSON 示例**均为合成数据**:
模型名统一为 `demo-*`,客户端为 `vscode` / `webconsole` / `sdk`,Prompt 为通用示例文本,
审计 IP 取自 RFC 5737 的文档专用网段(`192.0.2.0/24`)。
生成方式是 `tools/demo_data.py`,所以任何人不需要真实账号就能复现整套文档。
### 致谢
- 交互大屏依赖 [Apache ECharts](https://echarts.apache.org/)
- Web 框架 [Flask](https://flask.palletsprojects.com/),生产服务器 [waitress](https://github.com/Pylons/waitress)
- 行为准则框架来自 [Contributor Covenant](https://www.contributor-covenant.org/)
+67
查看文件
@@ -0,0 +1,67 @@
# 安全策略(Security Policy)
## 支持范围
本项目按「自托管、局域网内使用」的定位开发。安全修复只针对当前主分支与最新发布版本。
| 版本 | 是否接受安全修复 |
|---|---|
| `1.1.x`(当前) | ✅ |
| `< 1.1` | ❌ 请先升级 |
## 如何报告漏洞
**请不要在公开 Issue 里贴出可直接利用的细节**(含真实 Cookie、`secret_key`、可复现的绕过步骤)。
请通过以下任一私有渠道联系维护者:
<!-- TODO(维护者):首次公开发布前,把下面这行替换为真实可达的安全联系邮箱 -->
- 邮件:`<安全联系邮箱>`(占位,待维护者补全)
- 或通过代码托管平台(Gitea)的站内私信联系仓库管理员
请在报告里尽量包含:
1. 受影响的版本 / 提交号
2. 复现步骤与最小复现(可脱敏)
3. 影响范围(能读到什么、能改到什么)
4. 如果有,你建议的修复方向
我们会在 **7 天内**确认收到,并在修复发布后于 CHANGELOG 里致谢(除非你希望匿名)。
## 设计上已有的安全措施
理解这些边界,有助于你判断某个现象是「设计如此」还是「真的漏洞」:
| 项 | 做法 | 位置 |
|---|---|---|
| 全站鉴权 | 每个页面都有 `@login_required`,每个 `/api/*` 未登录返回 401 JSON | `workbuddy_portal/security.py`、`web/views.py` |
| CSRF | 所有写请求必须带 `X-CSRF-Token`,页面注入 `window.WB_CSRF`,服务端统一拦截 | `security.check_csrf` |
| 会话签名 | Flask `secret_key` 由 `data/instance.json` 持有,首次启动随机生成 | `workbuddy_portal/config.py` |
| 口令存储 | 加盐哈希,不存明文 | `security.hash_password` |
| 凭据脱敏 | 云端 **Cookie 是账号凭证**:入库后页面与接口**都不回传全文**,只给「N 字符,结尾 …xxxx」 | `config.SECRET_KEYS`、`web/views.py` |
| 开放重定向 | 登录后的 `next` 只允许站内相对路径 | `web/views.py` |
| TLS 校验 | 默认开启,**不提供「关掉校验」的快捷开关**(Cookie 不该裸奔) | `settings.ssl_verify` |
| 容器权限 | 运行层非 root(uid/gid 1000 `app`) | `Dockerfile` |
**绝不入库**:`data/instance.json`(含 `secret_key`)、`data/usage.sqlite`、`logs/*`、`.env`。
`.gitignore` 已覆盖;改动忽略规则后请用 `git check-ignore -v <file>` 逐条复核。
## 已知的**非**目标(部署方需自行处理)
本项目刻意不做下面这些,请按你的环境补齐:
- **没有多租户与细粒度权限**:登录用户即为管理员,能改配置、能看全部数据。
- **没有限流与账号锁定**:公网暴露前请置于反向代理的 rate limit 之后。
- **没有强制 HTTPS**:请由反向代理(nginx/Caddy)终止 TLS。
- **没有备份机制**:备份策略需要你自己定(见 `docs/DEPLOYMENT.md`)。
- **不建议直接暴露到公网**:设计前提是局域网或 VPN 内使用。
- **Cookie 的获取方式由使用者负责**:手动从浏览器复制。请勿把该 Cookie 分享给他人,
它的权限等同于你的账号;轮换后记得在「配置管理」页更新。
## 部署前的最小检查清单
- [ ] 已修改默认管理员口令(`WB_ADMIN_PASSWORD`),不再是 `admin123`
- [ ] `data/` 与 `logs/` 目录的权限只对服务账号可读写
- [ ] 前面有反向代理并启用了 HTTPS
- [ ] 确认 `data/instance.json` 没有被提交到任何仓库
- [ ] 已规划备份(SQLite 库是唯一正本)
+78
查看文件
@@ -0,0 +1,78 @@
# 第三方组件与许可声明(Third-Party Notices)
本项目(WorkBuddy Portal)自身以 [MIT 许可证](LICENSE) 发布。
但它**依赖**、并在个别位置**再分发**了若干第三方组件。这些组件的著作权归各自作者所有,
其许可条款独立于本项目的 MIT 条款。本文件汇总这些依赖,供合规审查与二次分发时参考。
---
## 一、运行期依赖(`requirements.txt`)
这些包不在本仓库内,由使用方安装时获取。
| 组件 | 版本要求 | 许可证 | 用途 |
|---|---|---|---|
| [Flask](https://flask.palletsprojects.com/) | `>=3.0` | BSD-3-Clause | Web 框架(路由、Jinja 模板、会话) |
| [waitress](https://github.com/Pylons/waitress) | `>=3.0` | ZPL-2.1 | 生产级纯 Python WSGI 服务器 |
| [openpyxl](https://openpyxl.readthedocs.io/) | `>=3.1` | MIT | 仅 `manage.py import-xlsx` 读 Excel |
Python 标准库(`sqlite3`、`urllib`、`http`、`threading` 等)按 PSF-2.0 许可,随 Python 分发。
> 项目**刻意不依赖** `APScheduler`(调度自实现)与 `requests`(用标准库 `urllib`),
> 因此这两者的许可证与本项目无关。
## 二、随仓库再分发的第三方资源
这是需要特别注意的一类:文件**物理存在于本仓库中**。
### Apache ECharts 5.6.0 — Apache License 2.0
- **位置**:`workbuddy_portal/web/static/dashboard/vendor/echarts.min.js`
- **著作权**:Copyright © 2017-2025 Apache Software Foundation 及 ECharts 贡献者
- **许可证**:Apache License, Version 2.0(全文见 <https://www.apache.org/licenses/LICENSE-2.0>)
- **为何内置**:用量大屏要在局域网内离线可用,不能依赖公网 CDN
- **未修改**:文件按官方发行版原样保留,其头部已包含 Apache 许可证声明与版权信息
按 Apache-2.0 第 4 条要求,再分发时需保留许可证与版权声明——该 `.min.js` 文件头部已自带,
本声明构成附加的显著声明。
> **替换说明**:如需升级,从 <https://echarts.apache.org/> 下载对应版本覆盖同名文件即可,
> 大屏页通过 `/static/dashboard/vendor/echarts.min.js` 引用,无需改代码。
### 项目自有资源
以下文件由本项目创作,同样按 MIT 发布,**不属第三方**:
- `workbuddy_portal/web/static/favicon.svg`
- `workbuddy_portal/web/static/css/app.css`
- `workbuddy_portal/web/static/js/app.js`
- `workbuddy_portal/web/static/dashboard/index.html`
- `docs/images/*.png`(界面截图,**使用合成示例数据**渲染,见下节)
## 三、开发期工具(非运行依赖)
| 组件 | 许可证 | 用途 |
|---|---|---|
| [Playwright for Python](https://playwright.dev/python/) | Apache-2.0 | `tools/shots.py` 登录后逐页截图 |
| [Pillow](https://python-pillow.org/) | MIT-CMU | 人工压缩文档配图时使用,未入库 |
这些工具**不会**被打进 Docker 运行镜像的依赖里,也不影响部署方的义务。
## 四、文档与截图中的数据
`docs/images/` 下的界面截图与 `docs/` 中的 JSON 示例**全部使用合成数据**,
由 `tools/demo_data.py` 生成:模型名统一为 `demo-*`,客户端为 `vscode`/`webconsole`/`sdk`,
Prompt 为通用示例文本,审计 IP 取自 RFC 5737 的文档专用网段(`192.0.2.0/24`)。
**不含任何真实账号、真实用量或第三方受版权保护的内容。**
---
## 五、合规自查清单
二次分发或商用前,建议逐项确认:
- [ ] `LICENSE` 与本文档随发行物一并提供
- [ ] `vendor/echarts.min.js` 的头部许可证声明未被剥离或压缩掉
- [ ] 若替换了 ECharts,同步更新本文档中的版本号
- [ ] 若新增了第三方文件到仓库,在此登记其许可证
- [ ] 若将本项目的界面截图用于宣传,确认其中不含真实业务数据
+3
查看文件
@@ -1,4 +1,7 @@
#!/bin/sh #!/bin/sh
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
# ============================================================================= # =============================================================================
# workbuddy-portal 容器入口 # workbuddy-portal 容器入口
# 1) 幂等初始化数据库(建表 + 默认配置 + 首个管理员) # 1) 幂等初始化数据库(建表 + 默认配置 + 首个管理员)
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""容器健康检查。 """容器健康检查。
只用标准库:slim 镜像里没有 curl。`/login` 是唯一免登录页面,拿到 200 即认为 只用标准库:slim 镜像里没有 curl。`/login` 是唯一免登录页面,拿到 200 即认为
+16 -1
查看文件
@@ -22,16 +22,31 @@
- **容器环境变量**:`WB_HOST` `WB_PORT` `WB_DATA_DIR` `WB_LOG_DIR` `WB_DB` - **容器环境变量**:`WB_HOST` `WB_PORT` `WB_DATA_DIR` `WB_LOG_DIR` `WB_DB`
`WB_ADMIN_USER` `WB_ADMIN_PASSWORD` `WB_DISABLE_SCHEDULER` `WB_IMPORT_CREDS` `WB_IMPORT_XLSX` `WB_ADMIN_USER` `WB_ADMIN_PASSWORD` `WB_DISABLE_SCHEDULER` `WB_IMPORT_CREDS` `WB_IMPORT_XLSX`
- **文档体系** `docs/`: - **文档体系** `docs/`:
[用户使用手册](USER-GUIDE.md)(含 9 张界面截图)、 [用户使用手册](USER-GUIDE.md)(含 9 张界面截图,**全部用合成示例数据渲染**)、
[部署与运维指南](DEPLOYMENT.md)(含推镜像到 Gitea 注册表的完整流程)、 [部署与运维指南](DEPLOYMENT.md)(含推镜像到 Gitea 注册表的完整流程)、
[架构与设计说明](ARCHITECTURE.md)、 [架构与设计说明](ARCHITECTURE.md)、
[接口参考](API.md)、 [接口参考](API.md)、
[常见问题](FAQ.md) [常见问题](FAQ.md)
- **开源声明体系**(仓库根目录):[LICENSE](../LICENSE)(MIT)、
[THIRD-PARTY-NOTICES.md](../THIRD-PARTY-NOTICES.md)(依赖清单与再分发合规自查)、
[CONTRIBUTING.md](../CONTRIBUTING.md)(含「必须遵守的不变量」与五层自检方法)、
[SECURITY.md](../SECURITY.md)、[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)、
`.github/` 下的 Issue 表单与 PR 模板、`.editorconfig`,
以及给全部 Python / Shell 源文件加 `SPDX-License-Identifier: MIT` 头
- **`tools/demo_data.py`**:生成**完全合成**的示例库(假模型名 / 假 Prompt / 偏斜的积分分布),
写入 `data/demo/`(已在 `.gitignore` 内)。文档截图与本地调试都基于它,
任何人不需要真实账号就能复现整套界面
- **`.gitattributes`**:强制 `*.sh` / `Dockerfile` / 各类源码为 LF - **`.gitattributes`**:强制 `*.sh` / `Dockerfile` / 各类源码为 LF
(带 CRLF 的 `.sh` 在容器里会报 `exec format error`,极难定位) (带 CRLF 的 `.sh` 在容器里会报 `exec format error`,极难定位)
### 变更 ### 变更
- **文档数据脱敏**:9 张界面截图全部改用合成示例数据重拍;
`docs/API.md`、`docs/DEPLOYMENT.md` 示例响应里的真实模型名与真实统计数字一并替换为示例口径。
此前截图中含**真实 Prompt 全文**、本机路径与本机用户名,属于不该公开的内容
- **`.gitignore` 补强**:只写 `data/*.sqlite` 会漏掉子目录,改为同时保留 `data/**/*.sqlite`
等规则,并新增 `data/demo/` 忽略——否则 `tools/demo_data.py` 的产物会被误提交
- **项目定名**:`wb_usage_portal` → **`workbuddy-portal`**; - **项目定名**:`wb_usage_portal` → **`workbuddy-portal`**;
Python 包 `wb_usage` → **`workbuddy_portal`**;会话 cookie Python 包 `wb_usage` → **`workbuddy_portal`**;会话 cookie
`wb_usage_sid` → `workbuddy_portal_sid`(升级后需要重新登录) `wb_usage_sid` → `workbuddy_portal_sid`(升级后需要重新登录)
+3
查看文件
@@ -1,5 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""WorkBuddy Portal —— 统一命令行入口。 """WorkBuddy Portal —— 统一命令行入口。
采集 / 存储 / 呈现三件事都由本项目承担,不再依赖外部计划任务或自动化。 采集 / 存储 / 呈现三件事都由本项目承担,不再依赖外部计划任务或自动化。
+4 -1
查看文件
@@ -1,5 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""端到端验收:对**运行中的**服务发真实 HTTP 请求,走完整登录/CSRF/API 链路。 """端到端验收:对**运行中的**服务发真实 HTTP 请求,走完整登录/CSRF/API 链路。
与 tests 里用 Flask test_client 的冒烟测试互补——这里验证的是「真的起起来了、 与 tests 里用 Flask test_client 的冒烟测试互补——这里验证的是「真的起起来了、
@@ -7,7 +10,7 @@
用法: 用法:
python tools/check_live.py # 默认 http://127.0.0.1:8848 python tools/check_live.py # 默认 http://127.0.0.1:8848
python tools/check_live.py --base http://10.0.0.5:8848 python tools/check_live.py --base http://192.168.1.50:8848 # 换成你的部署主机
python tools/check_live.py -u admin -p 你的密码 python tools/check_live.py -u admin -p 你的密码
python tools/check_live.py --from 2026-09-08 --to 2026-09-14 python tools/check_live.py --from 2026-09-08 --to 2026-09-14
+3
查看文件
@@ -1,4 +1,7 @@
#!/bin/sh #!/bin/sh
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
# ============================================================================= # =============================================================================
# 用 Gitea Access Token 一次性完成:推代码 + 推镜像 # 用 Gitea Access Token 一次性完成:推代码 + 推镜像
# #
+3
查看文件
@@ -1,5 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""界面实检:登录后逐页截图,用来目视确认「统一美化」是否真的落地。 """界面实检:登录后逐页截图,用来目视确认「统一美化」是否真的落地。
用法: 用法:
+3
查看文件
@@ -1,5 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""离线回归:用 Flask test_client 对**真实库**做全页面只读渲染 + 缺陷防回归断言。 """离线回归:用 Flask test_client 对**真实库**做全页面只读渲染 + 缺陷防回归断言。
与 tools/check_live.py 的分工: 与 tools/check_live.py 的分工:
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""WorkBuddy Portal —— 独立 Flask 项目。 """WorkBuddy Portal —— 独立 Flask 项目。
一个程序管全部: 一个程序管全部:
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""云端用量接口客户端(纯 urllib,不依赖 requests/浏览器)。 """云端用量接口客户端(纯 urllib,不依赖 requests/浏览器)。
接口:POST {api_base}/billing/meter/get-user-request-usage 接口:POST {api_base}/billing/meter/get-user-request-usage
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""采集主流程:云端增量 -> SQLite(去重、断点、漂移校验、运行记录)。 """采集主流程:云端增量 -> SQLite(去重、断点、漂移校验、运行记录)。
与原 fetch_usage.py 的差别: 与原 fetch_usage.py 的差别:
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""基础配置。 """基础配置。
刻意保持「薄」:凡是运行期要改的东西(cookie、调度周期、采集参数)都放数据库 刻意保持「薄」:凡是运行期要改的东西(cookie、调度周期、采集参数)都放数据库
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""SQLite 访问层。 """SQLite 访问层。
并发约定(重要): 并发约定(重要):
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""SQL 聚合层:所有统计都在 SQLite 里算完再出去,页面不再搬运全量明细。 """SQL 聚合层:所有统计都在 SQLite 里算完再出去,页面不再搬运全量明细。
返回结构刻意与旧版 dashboard/data/*.json 的字段保持一致(d/c/k/fc/bc/m/h、 返回结构刻意与旧版 dashboard/data/*.json 的字段保持一致(d/c/k/fc/bc/m/h、
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""进程内调度器(手写,不依赖 APScheduler)。 """进程内调度器(手写,不依赖 APScheduler)。
为什么不引 APScheduler: 为什么不引 APScheduler:
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""密码哈希、登录装饰器、CSRF。 """密码哈希、登录装饰器、CSRF。
局域网可访问 ⇒ 必须有鉴权。这里用 Werkzeug 自带的 PBKDF2,不引第三方依赖。 局域网可访问 ⇒ 必须有鉴权。这里用 Werkzeug 自带的 PBKDF2,不引第三方依赖。
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""Web 层包:蓝图注册。""" """Web 层包:蓝图注册。"""
from . import api, views from . import api, views
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""JSON API —— ECharts 大屏与后台页面的数据入口。 """JSON API —— ECharts 大屏与后台页面的数据入口。
约定: 约定:
+3
查看文件
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Wang Chuanli
"""页面路由(Jinja 模板)。 """页面路由(Jinja 模板)。
分工: 分工: