# VideoRoll 项目审计报告 ## 需要立即修复 (Critical) ### 1. Fernet 密钥管理缺失 - **文件**: `src/videoroll/utils/fernet.py` - 密钥文件不存在时自动生成新密钥,无备份/轮转机制 - 密钥丢失 = 所有加密的 YouTube cookies、Bilibili tokens、OpenAI API keys 永久不可恢复 - 无密钥轮转支持,`@lru_cache` 使密钥在进程生命周期内无法刷新 - **改进**: 启动时检查密钥是否存在,不存在则报错退出而非静默生成;支持密钥轮转 ### 2. 登录接口无防暴力破解 - **文件**: `src/videoroll/apps/orchestrator_api/main.py` (`/auth/login`) - 无速率限制,可无限次尝试密码 - PBKDF2 200k 迭代提供了一定的单次计算成本,但不足以作为唯一防线 - **改进**: 加入 `slowapi` 或类似限流库,限制同一 IP 的登录尝试频率 ### 3. Remote API token 通过 query 参数传递 - **文件**: `src/videoroll/apps/orchestrator_api/remote_api_settings_store.py` - `?token=` 出现在 access log、代理日志、浏览器历史、Referer header 中 - token 虽然在存储时做了 PBKDF2 hash,但传输过程中明文暴露 - **改进**: 改用 `Authorization: Bearer` header 传递 token --- ## 高优先级 (High) ### 4. orchestrator_api/main.py 是 God File - **文件**: `src/videoroll/apps/orchestrator_api/main.py` (2900+ 行) - 80+ 路由、CORS 配置、认证中间件、后台线程(清理、YouTube 扫描)全部在一个文件中 - 错误处理不统一:有的直接 `raise HTTPException`,有的 `try/except httpx.HTTPError`,有的 `except Exception: pass` - `_ingest_youtube_source` 在多处被调用,但内部自行创建 `httpx.Client` 和 `SessionLocal`,无法参与外部事务 - **改进**: 按职责拆分为独立 router 模块(auth、tasks、settings、youtube、maintenance);统一错误处理中间件 ### 5. Celery worker 无重试和确认机制 - **文件**: `src/videoroll/apps/subtitle_service/worker.py` - `process_job`、`process_render_job`、`task_queue_tick` 均未使用 `autoretry_for`、`retry()`、`acks_late` - worker 被 OOM/SIGKILL 杀掉后任务直接丢失,仅有的恢复逻辑 `_recover_interrupted_subtitle_jobs()` 将任务标记为 FAILED 而非重试 - 翻译重试使用 `while True` + `time.sleep()` 阻塞 worker 线程 - **改进**: 加 `acks_late=True` + `autoretry_for` 或手动 `retry()`;翻译重试改用 Celery 内置 retry 机制 ### 6. 容器以 root 运行 - **文件**: `Dockerfile` - 无 `USER` 指令,容器进程以 root 身份运行 - 应用被攻破后攻击者拥有完整 root 权限 - **改进**: 加 `RUN useradd -r -s /usr/sbin/nologin videoroll` 和 `USER videoroll` ### 7. 内部服务间认证 token 从 S3 secret 派生 - **文件**: `src/videoroll/utils/internal_api_token.py` - token = `SHA256("videoroll-internal-token:v1:" + s3_secret_access_key)` - S3 secret 轮转后所有内部认证同时失效,无独立的内部密钥配置 - 无 HMAC、无 per-service nonce、无过期机制 - **改进**: 独立的 `INTERNAL_API_SECRET` 环境变量,不与 S3 secret 绑定 ### 8. 安全相关 cookie 配置问题 - **文件**: `src/videoroll/apps/orchestrator_api/main.py` - 设备 cookie 有效期 180 天,无会话撤销机制,被盗 cookie 可用半年 - 非 HTTPS 时不设 `Secure` 标志,cookie 以明文传输 - `allow_methods=["*"]` + `allow_credentials=True` 配置过于宽松 - 密码 hash 缓存在 app.state 中,改密码后旧 hash 不会立即失效 - **改进**: 缩短 cookie 有效期;始终设 `Secure`;限制 CORS methods/headers;密码变更时清理缓存 --- ## 中优先级 (Medium) ### 9. Auto-migration 功能受限 - **文件**: `src/videoroll/db/auto_migrate.py` - 只能 `ALTER TABLE ADD COLUMN`,无法处理:重命名列、改类型、建新表、加约束/索引、删列、数据迁移 - `@lru_cache` 使 rolling deploy 时旧进程不会执行新 migration - `_add_column` 使用字符串拼接构造 SQL,虽然当前是硬编码值但存在注入隐患 - **改进**: 如果 schema 变更频繁,考虑引入 Alembic;至少将 SQL 构造改为参数化 ### 10. 代码重复 - `_effective_youtube_settings` 在 `orchestrator_api/main.py` 和 `subtitle_service/worker.py` 中重复 - `_read_s3_bytes` 在两个文件中几乎完全相同 - `_safe_append_log_line`、`_safe_append_log_block`、`_cleanup_local_work_root` 等辅助函数仅存在于 worker.py 但其他模块也需要 - **改进**: 提取到 `videoroll/utils/` 共享模块 ### 11. Docker entrypoint 单点故障 - **文件**: `docker/entrypoint.sh` - 三个进程并行运行(uvicorn + 2 celery workers),一个挂了整个容器退出,无重启逻辑 - 没有 `exec` 启动 uvicorn,SIGTERM 发给 bash 而非 uvicorn,信号处理可能异常 - 无健康检查 / readiness probe - **改进**: 考虑用 supervisord 或拆分为独立容器;对 uvicorn 使用 `exec` ### 12. 前端类型安全弱 - **文件**: `src/web/src/lib/types.ts`, `src/web/src/lib/http.ts` - `Asset.kind`、`SubtitleJob.status`、`PublishJob.state` 用 `string` 而非联合类型 - `fetchJson` 做 `as T` 强转,无运行时校验,API 响应变化时静默产生错误数据 - 部分页面用 `alert()`/`confirm()` 做用户反馈 - **改进**: 将已知枚举定义为 union types;关键 API 响应加 zod 运行时校验 ### 13. 依赖版本只有下限没有上限 - **文件**: `pyproject.toml` - `cryptography>=42.0.0`、`fastapi>=0.115.0`、`yt-dlp>=2026.2.4` 等均无上界 - 未来 `pip install` 可能拉入不兼容或有安全问题的版本 - **改进**: 对安全敏感的包(cryptography、sqlalchemy、psycopg)加 `