mufasa159/notes-web
GitHub: mufasa159/notes-web
一款零知识、端到端加密的自托管笔记应用,采用混合后量子加密方案,确保服务器仅存储密文而明文始终留在浏览器中。
Stars: 0 | Forks: 0
# 笔记
端到端加密笔记应用。本地优先、零知识、混合后量子(RSA-4096 + ML-KEM-768)。服务器仅存储密文;明文绝不离开浏览器。威胁模型请参见 [SECURITY.md](./SECURITY.md),架构请参见 [CLAUDE.md](./CLAUDE.md)。
## UI 演示
https://github.com/user-attachments/assets/12d5d58d-af71-439f-92b6-297bdc01e8c0
## 快速开始
需要 Python 3.12+。
```
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.py # http://127.0.0.1:8000
NOTES_DEBUG=1 python app.py # uvicorn --reload + ./.notes/ for state
```
第一个注册的用户将成为管理员。
## 目录结构
```
app.py, config.py, storage.py, migrate.py entry points
auth/ login, register, sessions, tokens, lockout
notes/ /api/notes + /api/folders
audit/ encrypted-at-rest, hash-chained log
admin/ /admin/users, /admin/audit, /admin/settings
crypto/ server-side rotation payload validation
db/ shared async aiosqlite helpers
jobs/ background tasks (backups, chain verify, retention)
middleware/ access, csrf, headers, ratelimit, ip allowlist, validation
migrations/ numbered .sql + .py files
templates/, static/, tests/, docs/
```
模块级详情请参见 [CLAUDE.md](./CLAUDE.md),设计文档请参见 [docs/](./docs/)。
## 环境变量
| 变量 | 默认值 | 作用 |
|---|---|---|
| `NOTES_HOME` | `~/.notes` | 状态数据(DB、密钥、备份、日志)的存放位置。 |
| `NOTES_DEBUG` | 未设置 | 当为 `1` 时:启用 Starlette 调试模式 + uvicorn `--reload`,将状态固定到 `./.notes/`,并移除 `Secure` cookie 属性。 |
| `NOTES_INSECURE_COOKIES` | 未设置 | 当为 `1` 时:移除 `Secure` cookie 属性。用于未开启 `NOTES_DEBUG` 的 HTTP 部署环境。 |
| `NOTES_AUDIT_KEY` | 未设置 | 当为 `file` 时:强制将审计日志密钥存储到磁盘(`~/.notes/keys/audit.key`,权限模式 0600),而不是 OS 密钥链。 |
| `NOTES_DISABLE_BACKGROUND_JOBS` | 未设置 | 当为 `1` 时:跳过备份、链验证和保留任务。 |
## 加密
- 浏览器中的 KDF:使用 Argon2id 对密码进行处理,通过 HKDF-SHA256 拆分为 `auth_hash`(发送至服务器)和 `wrap_key`(保留在本地)。
- 每个用户持有两个在浏览器中生成的密钥对:RSA-OAEP-4096 和 ML-KEM-768。
- 每条笔记都有一个独立的 AES-256-GCM 密钥,由上述两个公钥进行封装。
- 密钥每 90 天会在登录时通过 Web Worker 自动轮换(可配置)。
- 审计日志使用 AES-256-GCM 加密、哈希链式连接,且 60 天内不可变。
各个流程的序列图和活动图请参见 [docs/diagrams/](./docs/diagrams/)。
## 数据库迁移
Schema 位于 `migrations/` 目录,按 `NNN_name.{sql,py}` 格式编号。运行器会在启动时应用待处理的文件,如果校验和发生变动或出现编号间隔,则会拒绝运行。迁移发布后切勿修改;请编写新的迁移。
```
python migrate.py --status # applied vs pending
python migrate.py # apply pending
```
## 测试
```
pytest # full suite
pytest tests/auth # one domain
```
## 构建(生产环境资源)
`static/{js,css}` 下的源文件在开发环境中按原样提供。对于生产环境,运行打包器以在 `static/dist/` 下生成带有内容哈希的副本以及 `manifest.json`:
```
python build.py # write static/dist/
python build.py --clean # wipe and rebuild
```
CSS 会被压缩;JS 模块在复制时会加上哈希文件名,并且其相对导入路径会被重写。Vendor(`static/vendor/*`)和 Monaco 资源不参与打包。模板通过 Jinja `static_path()` 辅助函数解析资源,当 manifest 存在且未设置 `NOTES_DEBUG` 时,它会选择 `/static/dist/...`,否则回退到源路径。
## 供应链
三层完整性保护,从小到大:
**1. 哈希锁定的 Python 依赖。** `requirements.lock` 将每一个直接和传递依赖锁定到精确版本及 wheel/sdist 的 sha256 哈希。生产环境安装使用:
```
pip install --require-hashes -r requirements.lock
```
`--require-hashes` 会让 pip 拒绝任何摘要不匹配的 wheel——无论是被篡改的 PyPI 镜像还是遭到入侵的维护者账户,都无法暗中替换发布版本。`requirements.txt` 仍然是人类可读的可靠信息源(带有版本锁定的直接依赖)。要在编辑 `requirements.txt` 后重新生成锁定文件,请参见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
**2. 内容哈希的 dist 文件名。** `python build.py` 将 `static/js/*.js` 和 `static/css/*.css` 重写为 `static/dist/..`。URL 中的哈希就是文件的内容指纹,因此任何字节的改变都会强制生成新的 URL——在发布安全修复后,客户端无法提供缓存的旧 payload 副本。
**3. Vendor 脚本的子资源完整性 (SRI)。** `build.py` 还会写入 `static/dist/sri.json`,其中包含每个直接加载的 vendor 入口点(argon2-browser、mlkem、monaco 加载器)的 sha384 哈希。模板会渲染 `