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 哈希。模板会渲染 `