Commit Graph

58 Commits

Author SHA1 Message Date
Nexus Agent 972456a246 feat(agent): 401 停心跳、配置重载后重启与升级双文件下发
提取 heartbeat_policy;install/upgrade 同步 heartbeat_policy.py;config reload 恢复心跳协程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 23:06:09 +08:00
Nexus Agent 69068e2e39 feat(ops): server_batch 回收、sync_logs 清理与 Agent 安装跳过
部署对齐三项后端能力:启动/周期收尾僵尸批量任务、30 天推送日志 purge、已安装且版本不低于主站时跳过批量安装 Agent。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:23:03 +08:00
Nexus Agent 08a0157c95 feat: 调度表单重构、登录门控、批量任务与页面缓存对齐
调度页执行周期可视化/单次执行/分类选机/推送源对齐;登录 IP 门控与无缝导航;
服务器批量后台任务、执行记录、凭据合并、各页激活刷新与错误提示修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:17:21 +08:00
Nexus Agent b8425cc059 fix(servers): 状态/心跳/Agent 列 overlay 后排序
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:38:28 +08:00
Nexus Agent 06af90d35e feat(scripts): 执行详情服务器名与顶栏居中提示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:20:37 +08:00
Nexus Agent 0b2d7ac50d feat(scripts): 异步执行、全局进度看板与完成提示音设置
脚本 exec 立即返回 running 并在后台跑批次;/ws/alerts 推送 script_progress/complete;
新增 ScriptRunsPage、Telegram 汇总通知与可配置的浏览器完成提示音。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:07:02 +08:00
Nexus Agent c435413563 feat(servers): 分类筛选/批量改分类、分页中文与列排序,脚本库按分类选机
支持 300+ 台服务器按分类浏览与批量管理,修复分页「全部」与排序;门控 Gate3 强制本地 API。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 04:30:24 +08:00
Nexus Agent c8b0663508 feat: 批量IP添加、脚本库历史、推送权限记录与脚本SSH执行修复
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
批量添加替代CSV并支持去重;脚本库页内嵌执行历史;推送历史记录 rsync 权限策略;
脚本执行不再依赖 Agent 在线;服务器同步日志与相关单测补齐。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 03:15:40 +08:00
Nexus Agent dfb0ea2d8f fix(frontend): 脚本库与推送调度对齐设计文档
修复 ScriptsPage 执行契约(script_id/command、历史过滤、长任务/凭据)及 SchedulesPage 缺失能力(target_path、next_run、push/script、cron/once、sync_mode);后端补迁移与 schedule_runner 传参。
2026-06-08 02:39:18 +08:00
Nexus Agent f12a0b6a36 fix(sync): rsync 推送后设置远程属主 www 与权限 755
推送默认 --chown=www:www --chmod=D755,F755,避免文件落盘为 root。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 02:23:43 +08:00
Nexus Agent 5ed3c28491 fix(sync): 推送异常时落库 failed,修复订正时区与卡住阈值
rsync 抛错时 sync_log 不再永久 running;reconcile 兼容 naive UTC。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 02:14:33 +08:00
Nexus Agent 7ea94a60ab fix(auth,servers): 修复 refresh 自动登出与服务器状态未知
API 返回 status 字段供前端显示在线状态;token_version 为 NULL 时 refresh cookie 含 :None 导致续期 401。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 22:53:29 +08:00
Nexus Agent 05006cb5df feat(auth): refresh token 先 Redis 后 MySQL 双写
登录 30 天会话 hash 持久化到 admin_refresh_tokens;启动从 MySQL 回填 Redis,
Redis 重启后会话仍可 refresh。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:56:56 +08:00
Nexus Agent a14fbb3df3 fix(ssh): 快速添加跳过主机密钥校验并中文化错误
凭据轮询在 SSH_STRICT 默认 true 时因 HostKeyNotVerifiable 在认证前失败;
探测始终 known_hosts=None,默认 strict 改为 false,错误信息统一中文。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:31:38 +08:00
Nexus Agent f9934bd4f1 feat(ops-patrol): Layer 3 巡检与 Telegram 配置打通
保存 Telegram 时同步至 .env,health_monitor 支持 MySQL 回退,
设置页展示巡检状态,部署时自动安装 cron。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:17:24 +08:00
Nexus Agent eab35a35be fix(security): BL-06 强制 Agent per-server key 并合并心跳单次查库
移除 Agent 认证与安装链路的 global API_KEY 回退,收口 risk-acceptance 接受项 2;巡检补修心跳重复 get_server 并加 assert_called_once 回归测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 11:17:41 +08:00
Nexus Deploy 4ba45abf38 feat(servers): 凭据轮询登录与连接失败列表
- 密码/SSH密钥预设增加用户名;快速添加(IP)轮询全部预设尝试 SSH 登录
- 成功入 servers 列表;失败写入 pending_servers 并支持重试/删除
- 新增 credential_poller、try_ssh_login 与 add-by-ip/pending API
2026-06-06 20:08:48 +08:00
Nexus Deploy b866e944ab fix: Code Review P0/P1 batches 1-3 and single-operator risk acceptance
Apply sync/install/auth/schedule/retry/agent/settings fixes from full
code review; document accepted WS and Agent legacy risks for solo ops.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:57:49 +08:00
Nexus Deploy a2ae74d582 release: BUG fixes batch 1-6, full bug scan docs, Ubuntu/Linux dev
- Backend: auth refresh reuse, schedule/retry/sync/agent/files fixes
- Frontend: push WS, files ETag browse, vite build assets
- Docs: audit/changelog, production deploy gate, bug scan registry B7-77
- Deploy: deploy-production.sh, prune assets, gate logs
2026-06-04 14:01:14 +08:00
Your Name f078d0e03b feat(push): localize sync/rsync error messages to Chinese in history and progress
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 15:18:01 +08:00
Your Name 78798e3630 feat(push): refactor B0-B6, /ws/sync channel, staging cleanup
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 08:14:09 +08:00
Your Name c6f1d73ff5 feat(files): P4 GET browse with ETag and P5 zero-flicker loading
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 05:59:55 +08:00
Your Name 928e3c5fdb fix: 登录白名单 IP 绕过防暴破锁定
白名单内 IP 跳过失败次数锁定;失败仅审计不计数;成功登录清除该用户历史失败记录。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:37:22 +08:00
Your Name 832e34fd98 fix: 修复 6 个 MEDIUM 级 Bug (锁定/重试/调度/分页/SSH池/token_version)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:29:06 +08:00
Your Name 19cfb7caaa fix: 系统全扫描修复 7 个 HIGH 级 Bug
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:22:00 +08:00
Your Name d93c518a14 feat(files): POSIX paths, elevation, recursive chmod, access hints and docs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:54:18 +08:00
Your Name 8311821590 fix: 全量修复批量选择、JWT 60min、script-callback 认证与心跳数据流
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 12:17:47 +08:00
Your Name 6183047fd6 fix: 全站 bug 审查修复 (前端9项 + 后端5项)
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
前端修复:
- P0: App.vue http 未导入导致全局搜索崩溃
- P1: TOTP 登录流程断裂 (TotpRequiredError 非 ApiError 子类)
- P1: ServersPage 编辑服务器 domain 被端口字符串污染
- P1: PushPage Set[0] 无效 → values().next().value
- P1: SettingsPage API 返回字符串未转数字
- P2: api/index.ts getList 重复定义移除
- P2: LoginPage 锁定倒计时 now ref + watch 更新
- P2: TerminalPage 路径验证正则放松 (允许空格/中文)
- P2: useWebSocket CONNECTING/CLOSING 状态关闭旧连接

后端修复:
- P1: websocket.py redis.keys() → scan_iter() 避免阻塞
- P1: auth_service.py TTL 仅在 key 无 TTL 时设置
- P1: servers.py 批量操作加 asyncio.Lock 避免并发 session commit
- P2: settings.py asyncio.get_event_loop() → get_running_loop()
- P2: settings.py datetime.utcfromtimestamp() → datetime.fromtimestamp(tz=utc)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:24:55 +08:00
Your Name a50f17941d fix: settings 安全加固 + UX 迭代 — 10项修复
安全修复:
- S-01 SSRF: parse-subscription 加私有IP封禁+重定向限制+响应大小限制
- S-02 值校验: PUT /{key} 加 key 白名单 + INT 范围校验(1-1000/1-100/10-600)
- S-05 重登录: 改密码成功后跳转login.html+绿色提示
- S-07 审计: add_manual_ips/remove_allowlist_ip 补全审计日志
- S-09 TOTP: 已启用TOTP时改密码需输入验证码

UX改进:
- UX-01: 密码框focus ring + 设置项input type映射(number/url/text)
- UX-02: 改密码按钮loading状态(disabled+提交中...)
- UX-03: 3处空catch块→console.error+toast
- UX-04: 保存失败时reloadSettings恢复原值
- UX-05: IP格式校验(前端正则+后端Pydantic model_validator)
2026-05-30 21:58:13 +08:00
Your Name 32feb1b6db fix: 全站 ruff 清零 — 77 errors → 0
Fixes:
- F821: install.py site_url 未定义(NameError)、sync_v2.py os 未导入、script_jobs.py LOG f-string
- F401: 移除 22 个未使用导入(models/__init__.py、install.py、auth.py 等)
- B904: 20 个 except 块 raise 添加 from e/from None
- S110: 20 个有意的 try-except-pass 添加 noqa 注释(SSH清理/DDL幂等/WS断连)
- B007: 3 个未使用循环变量重命名(dirs→_dirs、server_id→_server_id)
- F541: 3 个无占位符 f-string 修正
- F841: auth.py 未使用 ip_address 变量移除
- S105: auth_service.py Redis key prefix 误报 noqa
- B023: script_execution_flush.py 循环变量绑定修复

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:07:45 +08:00
Your Name 0b82a3fee7 feat: 推送取消 — 引擎 cancel flag 检查 + WS cancelled 计数器
sync_engine_v2: 每台服务器推送前检查 Redis sync:cancel:{batch_id},
若已取消则跳过并标记 cancelled 状态,WS 广播 cancelled 计数。
websocket: broadcast_sync_progress 新增 cancelled 参数。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:57:38 +08:00
Your Name 152852e2c3 fix: 会话时效30天 + 多设备同时登录
- access token 从 30 分钟延长到 30 天
- refresh token 从 7 天延长到 30 天
- refresh token 从 MySQL 单字段改为 Redis Set 存储,支持多设备同时登录
- 单设备退出不再踢掉其他设备(只删自己的 Redis hash)
- 改密码/禁用 TOTP 仍会让所有设备掉线(token_version 递增)
- 移除 reuse 误杀逻辑:版本不匹配只拒绝当前 token,不惩罚其他设备

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:52:14 +08:00
Your Name 74de9d303e feat: 推送页面 Round 2 迭代 — 同步说明 + 在线标识 + 失败重试 + Telegram通知 + 文件预览
F5: 同步模式详细说明 — 每种模式下方显示说明文字,切换时动态更新
F2: 服务器在线状态标识 — 服务器列表显示🟢/🔴在线/离线标识
F1: 失败自动重试 — 推送失败自动创建重试任务 + 失败行"🔄 重试"按钮
F3: 推送完成 Telegram 通知 — 全成功🟢/部分失败🟡/全失败🔴 三种消息含详情
F4: 文件内容预览 — 文件管理器点击文件名弹出内容预览模态框

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:59:30 +08:00
Your Name 82c297776a feat: 推送页面 5 项迭代 — WS实时进度 + 分组选择 + 历史增强 + 文件操作 + 推送校验
F1: broadcast_sync_progress() + batch_id + 前端WS逐台更新
F2: get_paginated() platform_id/node_id + /categories端点 + 筛选下拉框
F4: _sync_log_to_dict() 补全字段 + diff_summary捕获 + 可展开详情面板
F7: /local-file-ops端点 + 文件管理器删除/重命名
F8: /verify端点 md5sum对比 + 推送后校验UI

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:01:27 +08:00
Your Name 97be1fd214 完善审计日志中文化:auth_service/script_service/sync_engine 全部 detail 改中文 + 前端新增 webssh_token/refresh_token_mismatch 等映射 + MCP deploy 修复
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:57:51 +08:00
Your Name 3943ff4f3a fix: remove updated_at secondary check that invalidated fresh JWTs
The updated_at secondary check in _verify_token() was a "defense in
depth" mechanism, but it caused a race condition: login updates admin
(last_login, onupdate→updated_at) AFTER creating the JWT, so the JWT's
"updated" claim is immediately stale. Any DB admin update (including
innocuous ones like last_login) would invalidate all existing tokens.

The token_version primary check already covers all security scenarios
(password change, TOTP disable, token reuse detection) and is the
correct mechanism for invalidating sessions. Removed the updated_at
check from both _verify_token() and get_optional_admin().

Also reordered login/refresh flows to create JWT AFTER admin update,
so the token captures the latest updated_at (belt and suspenders).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 16:23:20 +08:00
Your Name deb3a94ee6 fix: token_version NULL handling — normalize None→0 in JWT comparison
Direct DB inserts (e.g. install wizard, manual MySQL) may leave
token_version as NULL. JWT payload had tv:null which failed the
strict != comparison against DB value 0. Normalize both sides
with `or 0` for defensive coding.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 15:37:10 +08:00
Your Name cdd0be328a fix: 六轮深度扫描 — 47项Bug修复、安全加固、死代码清理
Critical runtime bugs:
- terminal.html WebSSH完全不可用(URL前缀/JSON解析/Content-Type三处错误)
- servers.py路由遮蔽:/logs被/{id}拦截,3个前端页面同步日志查询失败
- scripts.html startExecPoll()→startExecPolling(),长任务快速执行崩溃
- agent.py {value!r!s:.50}格式串非法,agent发非数值时ValueError
- alerts.html d.daily.reduce()无null检查,API返回空数据时TypeError

Resource leak / stability:
- websocket.py僵尸连接未关闭TCP,文件描述符泄漏
- websocket.py _last_alert_time字典无限增长(加1小时过期清理)
- asyncssh_pool.py全忙时超过MAX_CONNECTIONS无限增长
- self_monitor.py Telegram告警无冷却,宕机时每30秒刷屏
- schedule_runner.py一次性调度执行超60秒会重复触发
- 限速脚本EXPIRE每次重置窗口可绕过(改用Lua原子脚本)

Security:
- JWT access token加token_version声明,改密码后旧token立失效(零宽限)
- INSTALL_MODE导入时常量→动态函数,安装后JWT认证不再残留禁用
- install.py /lock端点加管理员存在性验证,防止阻断安装
- ServerUpdate schema移除connectivity只读字段,防止伪造连接状态

Frontend fixes:
- doExec()缺r.ok检查、commands.html null检查
- _server_to_dict()补last_checked_at+ssh_key_public
- _field_match()逗号cron表达式修复
- alerts类型显示、SSH会话名称、搜索高亮定位
- 一次性/循环定时任务(run_mode+fire_at+自动禁用)

Dead code removed (400+ lines):
- SyncService batch_push/_push_single等5个方法(零调用者)
- 5个未使用schema(SyncCommands/SyncConfig/SyncSftp/FileDeploy/PaginatedResponse)
- 6个零调用service方法、3个无前端API端点
- 4个未使用import

Schema migrations:
- push_schedules: run_mode + fire_at列,cron_expr改NULL
- servers: 7个新列 + ssh_key_private/public VARCHAR(500)→TEXT

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-24 16:26:40 +08:00
Your Name 488838c989 feat: separate subscription/manual IPs + master enable/disable toggle
Data model (separate storage):
- login_allowlist_enabled: 'true'/'false' master switch
- login_subscription_url: URL to fetch every 2h
- login_subscription_ips: last fetch result (replaced entirely each refresh)
- login_manual_ips: manually added (never overwritten by refresh)
- login_allowed_ips: combined (used by auth for legacy compat)

ip_allowlist_refresh.py:
- Saves to login_subscription_ips (replace) + rebuilds login_allowed_ips

auth_service.py:
- Login check: only runs when LOGIN_ALLOWLIST_ENABLED is 'true'
- Combines subscription_ips + manual_ips at auth time (no stale combined key needed)

settings.py:
- POST /ip-allowlist/toggle: quick enable/disable endpoint
- GET /ip-allowlist: returns subscription_ips, manual_ips separately
- POST /ip-allowlist: saves manual_ips + optional subscription_url + optional enabled

settings.html:
- Toggle switch: enable/disable with warning if list is empty
- Green notice when enabled (warns about self-lockout risk)
- Grey notice when disabled
- Subscription IPs section: read-only, auto-refresh badge
- Manual IPs section: deletable per-IP, clear all button

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 18:19:47 +08:00
Your Name 5fcc1e22a6 feat: login IP allowlist with proxy subscription parser
server/infrastructure/subscription_parser.py (new):
- parse_subscription_content(): fetch and decode subscription, extract host/IP
- Supports SS, VMess, VLESS, Trojan, Hysteria2 formats
- Base64 decode with padding fix; IPv4/CIDR/hostname detection

server/config.py:
- LOGIN_ALLOWED_IPS setting (empty = allow all, comma-separated)

server/api/settings.py:
- POST /settings/ip-allowlist/parse-subscription: fetch URL, decode, return hosts
- POST /settings/ip-allowlist: save allowlist to settings table + reload
- GET /settings/ip-allowlist: return current list

server/application/services/auth_service.py:
- _ip_in_allowlist(): supports exact IP, CIDR (ipaddress module), hostname match
- login(): check allowlist before brute-force check; if IP blocked return
  reason='ip_blocked' with clear message; audit log 'login_ip_blocked'

web/app/settings.html:
- New '登录 IP 白名单' card with:
  - Subscription URL input + parse button (calls backend parser)
  - Parsed IPs with checkboxes (select/deselect before adding)
  - Manual IP/CIDR/hostname textarea input
  - Current allowlist with per-IP remove buttons
  - Clear all button (removes restriction)
  - Status badge: 启用 N条 / 未启用(不限制)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 18:11:23 +08:00
Your Name ac0eb79707 feat: rsync dry-run preview (方案D 智能提示型)
Backend:
- schemas.py: SyncPreview model (server_id, source/target_path, sync_mode, verbose)
- sync_engine_v2.py: preview_sync() + _parse_rsync_stats() helpers
  - runs rsync --dry-run --stats; verbose=True adds -v (capped 200 lines)
  - returns stats dict: files_transferred/created/deleted + byte counts
- sync_v2.py: POST /api/sync/preview endpoint with audit log

Frontend (push.html - 方案D):
- Sync mode radio change listener: onSyncModeChange()
- full/overwrite mode: orange warning card + prominent preview button
- incremental/checksum mode: subtle "预览首台变动 →" link
- Inline preview result section (no modal): stats cards + optional file list
- files_deleted shown in red card for destructive modes
- verbose toggle: "显示详细文件列表 ▼" triggers second request with verbose=true
- fmtBytes() helper for human-readable file sizes

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:41:47 +08:00
Your Name 6108d69b24 fix: Phase 4 audit fixes
F4a-01 script_callback_rate: INCR+EXPIRE were non-atomic; a crash between
       the two could leave the rate-limit key without a TTL, permanently
       blocking future callbacks for that job/IP. Fix: use Redis pipeline
       so INCR+EXPIRE are sent in one roundtrip and EXPIRE always executes.

F4a-02 sync_service: add comment documenting that StrictHostKeyChecking=no
       is a legacy issue in the dead-code SyncService shim; main sync path
       (SyncEngineV2) honours SSH_STRICT_HOST_CHECKING from settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:00:28 +08:00
Your Name 86bb32f87e fix: Phase 3h script_service + server_service
F3h-01 script_service._build_kill_command: shlex.quote(log_path) wraps the
       path in single-quotes; embedding that inside "..." produced a filename
       with literal single-quote chars that cat/kill could not find.
       Fix: use the shlex-quoted string directly without extra double-quotes.

F3h-02 script_service._substitute_db_vars: non-atomic string replacement
       allowed nested substitution when a credential value contained another
       $DB_* pattern. Fix: replace with null-byte sentinels first, then
       substitute actual values in a second pass.

server_service.push_to_servers: add comment documenting that audit_repo and
       retry_repo are None (internal compat shim — not used by routes).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:50:44 +08:00
Your Name c3c3bae370 fix: Phase 3g sync_engine_v2 bugs
F3g-01 sync_commands line 195: server_results is a list; unpacking it
       with ** raises TypeError, silently caught by gather(return_exceptions=True),
       so S2 command sync and S3 config sync returned empty results for all
       servers. Fixed by using a proper dict: {"server_name": ..., "results": ...}.

F3g-02 sync_config: config value containing newline chars was not stripped
       before shlex.quote; echo inside single-quotes would write multiple lines
       to the sysctl config file, injecting arbitrary keys. Strip \n\r\x00 from
       value before quoting (requires admin auth, defense-in-depth fix).

scripts.py, settings.py: CLEAN (no changes needed).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:46:52 +08:00
Your Name 33b7ed93bf fix: Phase 3e auth_service.py bugs
F3e-01 auth_service.py:180 - jwt_token_expires (MySQL tz-naive) vs
       datetime.now(timezone.utc) caused TypeError on token refresh;
       strip tzinfo before comparison when expires is tz-naive.

F3e-02 auth_service.py:271 - TOTP provisioning URI was not URL-encoding
       issuer or username; special chars (spaces etc.) broke QR scan.
       Use urllib.parse.quote() on both fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:41:50 +08:00
Your Name 752a24497c feat: Phase 2 security audit fixes + script execution platform
Security (P0/P1/P2):
- WebSSH: dedicated short-lived token, server_id binding, 4003 close code
- Remove /api/agent/exec from control plane (RCE surface eliminated)
- Global JWT middleware (JwtAuthMiddleware) with install-mode bypass
- decrypt_value: failure returns None, never leaks ciphertext
- Telegram: sanitize_external_message strips sensitive fields
- Agent auth: startup enforces non-empty API key, compare_digest
- Credentials: password_set bool flag, no plaintext in API responses
- audit_log: writes admin_username + ip_address on all CUD ops
- Install lock: all /api/install/* except GET /status return 403 post-install
- WebSocket dedup: publish once, subscribers deliver locally

Script execution platform:
- script_jobs.py / script_job_callback.py: async batching and callback
- script_execution_store.py / script_callback_rate.py: Redis-backed state
- script_execution_flush.py: background flusher to MySQL
- scripts.html / script-executions.html: full execution UI
- agent_url.py: centralised URL builder

Frontend:
- All 13 pages migrated from CDN to /app/vendor/ (no external deps)
- vendor/: alpinejs, tailwindcss-browser, xterm, xterm-addon-*, qrious
- Dashboard WebSocket real-time alerts; 8h JWT session timeout

Tests:
- test_security_unit.py: 15 unit tests (JWT, sanitize, vendor, install 403)
- test_api.py: env-configurable admin credentials

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:26:56 +08:00
Your Name d744d7df8e 安全与质量修复: P0-4/P0-5 + P1-6~P1-13 (8项)
P0-4: install.py _write_env() 值加引号转义,防止含#$\等字符的密码破坏.env格式
P0-5: install.py _build_redis_url() URL编码redis密码,防止@:等字符破坏URL解析
P1-6: auth_service.py _decode_access_token() 验证exp/sub声明存在,拒绝畸形JWT
P1-7: websocket.py + webssh.py WebSocket JWT验证增加updated_at检查,密码修改后令牌失效
P1-8: auth.py 无refresh_token的logout返回更明确的提示信息
P1-9: install.py create_admin增加_is_installed()检查,防止.env存在后重复创建
P1-10: servers.py server_stats() 改用SQL聚合查询,避免加载全部服务器对象
P1-11: heartbeat_flush.py 移除get_redis()永不返回None的死代码
P1-13: sync_engine_v2.py completed/failed计数器加asyncio.Lock防止并发竞态

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:04:40 +08:00
Your Name 938d26927f P1/P2: 后端安全与稳定性修复
- Agent per-server API Key认证 (agent.py)
- JWT updated_at校验与会话超时 (auth_jwt.py)
- 服务器凭据Fernet加密存储+密码脱敏 (servers.py)
- WebSSH服务器级授权检查 (webssh.py)
- Config同步去重+回滚机制 (sync_engine_v2.py)
- DB层分页offset/limit (server_repo.py)
- session.py logger修复 + @property死代码清理
- 心跳刷入rollback误用修复 (heartbeat_flush.py)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:28:57 +08:00
Your Name 8530f0e0d5 安全补强: 6项P0/P1漏洞修复
P0-1: PHP配置注入防护 — install.py添加_escape_php_string()转义单引号和反斜杠
P0-2: WebSocket JWT校验 — _verify_ws_token()要求exp+sub字段
P1-1: 删除SHA256密码fallback — auth_service.py和auth.py直接import bcrypt
P1-3: LIKE通配符转义 — search.py添加_escape_like()并对所有ilike()加escape参数
P1-2: 安全响应头中间件 — main.py添加SecurityHeadersMiddleware注入4个安全头
P0-3: Refresh Token重用检测 — Admin模型添加token_version字段,token格式改为token:admin_id:version

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:16:50 +08:00
Your Name c9a99f4fb3 fix: 全项目文档对齐 + 代码清理
代码修复:
- web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc)
- requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用)
- 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用)

连接池三层对齐 (config.py/.env.example/session.py):
- DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120
- 基于 MySQL max_connections=400, install.php 公式

文档修复 (21项):
- P0: 硬编码域名/IP替换为配置变量占位符
- P1: 10个过时设计文档加归档标注 (引用旧文件结构)
- P2: step-3-webssh报告 paramiko引用修正
- 6份审查报告连接池参数勘误 100/100→160/120
- ECC安全报告 EC5 datetime.utcnow 标记已修复
- docs/README.md 文档索引重写
- docs/memory/mem_nexus_overview.md 移除硬编码凭证
- docs/project/tech-stack-inventory.md paramiko标记已移除

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:19:56 +08:00