📊 审计概览
| 级别 | 数量 | 状态 |
|---|---|---|
| 🔴 Critical | 3 | ✅ 全部修复 |
| 🟡 High | 6 | ✅ 全部修复 |
| 🟢 P2 | 9 | ✅ 全部修复 |
总计: 18 项问题,全部处理完毕,编译通过,零警告。
🔴 Critical 级别问题
C1. Shell 命令注入漏洞(20+ 处)
影响文件: backup.rs, deployer.rs, ssh.rs, certbot.rs
问题描述: 多处直接将用户可控的路径/域名拼接到 shell 命令中,无引号转义。如果路径含空格或被注入 ; rm -rf /,将直接执行。
修复方案:
- 新增
ssh::shell_quote()函数(POSIX 标准单引号转义) - 所有 shell 命令中的变量必须用
shell_quote()包裹
/// Shell 安全转义:用单引号包裹字符串,内部单引号用 '\'' 拼接pub fn shell_quote(s: &str) -> String { format!("'{}'", s.replace('\'', "'\\''"))}修复统计:
ssh.rs:write_remote_file的cat >命令backup.rs: 2 处路径参数deployer.rs: 8 处路径/命令拼接certbot.rs: 15+ 处域名/路径/email/命令参数
C2. Certbot crontab 注入
位置: certbot.rs:173-181
问题描述: cron_line 含单引号,嵌套进 echo '{}' 破坏 shell 语法,导致 crontab 条目写入失败或被截断。
修复方案: 改用 printf '%s\n' + shell_quote 替代 echo '{}' 嵌套。
C3. 无 SSH 主机密钥验证
位置: ssh.rs 连接流程
问题描述: 未调用 session.known_hosts() 加载 known_hosts 文件,完全开放 MITM 攻击。
修复方案:
- 加载
~/.ssh/known_hosts文件 - 实现 TOFU 策略(Trust On First Use)
- 首次连接自动添加密钥并保存
- 密钥不匹配直接拒绝(防止 MITM)
match known_hosts.check(&host_with_port, key) { ssh2::CheckResult::Match => { /* 通过 */ } ssh2::CheckResult::NotFound => { /* TOFU:自动添加 */ } ssh2::CheckResult::Mismatch => { /* 拒绝:可能 MITM */ }}🟡 High 级别问题
H1. nginx.conf 被完全覆盖
位置: deployer.rs:84-94
问题描述: 生成的配置完全替换远程 nginx.conf,用户手动添加的 upstream、stream、自定义 log_format 等指令全部丢失。
修复方案: 仅在远程 nginx.conf 不存在时生成上传,已存在的主配置不会被覆盖。
H2. GUI IPC 部署结果丢失
位置: webview.rs:159-179
问题描述: deploy 方法立即回复 "started",最终结果和错误无人接收。
修复方案: 通过事件系统传递最终结果,确保 build:error/connect:error/upload:error 等都正确触发状态更新。
H3. EventLoop 忙轮询
位置: webview.rs:80
问题描述: 无条件 60fps 轮询(WaitUntil(16ms)),即使无 IPC 消息和窗口事件,CPU 也持续空转。
修复方案: 使用 AtomicBool 智能调度:
- 有待处理消息时:16ms 轮询
- 空闲时:1s 轮询
if has_pending.load(Ordering::Acquire) { *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(16));} else { *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::from_secs(1));}H4. Upload 无进度推送到 GUI
位置: upload.rs + webview.rs
问题描述: upload_dir 使用 indicatif::ProgressBar 输出到 stdout,在 GUI 模式下不可见。
修复方案:
- 新增
upload_dir_with_progress函数,支持进度回调 - GUI 模式下通过
push_event("upload:progress")推送进度
H5. ensure_remote_dir 逐文件调用
位置: upload.rs:44-47
问题描述: 每个文件上传前都对其父目录调用 ensure_remote_dir,1000 个文件 = 1000 次目录检查。
修复方案: 上传前一次性收集所有需要创建的目录,去重后批量创建。
H6. validate() 在 GUI 模式被绕过
位置: webview.rs:333-358
问题描述: build_config_from_params 直接构建 Config,不调用 validate(),导致认证信息缺失时报错不明确。
修复方案: 新增 config.validate_for_gui() 方法,deploy 前先验证配置。
🟢 P2 级别问题
| # | 问题 | 位置 | 修复方案 |
|---|---|---|---|
| P1 | 硬编码 npm run build | build.rs:9 | DeployConfig.build_command 可配置,通过 shell 执行任意命令 |
| P2 | HTML 注入不安全 | webview.rs:62-65 | safe_json_for_html 转义 <> 防止 </script> 注入 |
| P3 | dry-run 无实际实现 | webview.rs:181-183 | 实现完整模拟:验证本地目录、统计文件数、列出全部 6 步操作 |
| P4 | 上传无文件权限设置 | upload.rs:54-58 | 确认 sftp.create() 已默认 0o644,mkdir 用 0o755 |
| P5 | 重试间隔过短 | ssh.rs:29 | 改为指数退避 2^n 秒(2s/4s/8s),最大 30s |
| P6 | stderr 未读取 | ssh.rs:89-91 | exec() 新增 channel.stderr() 读取 |
| P7 | 无上传文件过滤 | upload.rs | 跳过 .git/node_modules/__pycache__/.DS_Store/Thumbs.db/*.map |
| P8 | #[allow(dead_code)] tcp 字段 | ssh.rs:12-13 | 改为 _tcp(Raii 命名约定),移除 #[allow(dead_code)] |
| P9 | 版本号硬编码在 HTML | index.html:462 | HTML 中 v0.1.0 → __INKS_VERSION__ 占位符,Rust 侧用 env!("CARGO_PKG_VERSION") 注入 |
📁 改动文件清单
| 文件 | 改动说明 |
|---|---|
src/ssh.rs | 新增 shell_quote()、SSH 主机密钥验证、_tcp 字段、指数退避重试、stderr 读取 |
src/backup.rs | 路径参数全部 shell_quote |
src/upload.rs | 新增 upload_dir_with_progress、批量目录创建、文件过滤(.git/node_modules 等) |
src/build.rs | 支持可配置构建命令 |
src/config.rs | 新增 build_command 字段、validate_for_gui() 方法 |
src/webview.rs | 智能 EventLoop 调度、版本号注入、dry-run 实现、GUI 配置验证、上传进度事件 |
src/nginx/deployer.rs | 路径参数全部 shell_quote、nginx.conf 不覆盖策略 |
src/nginx/certbot.rs | 全面修复 shell 注入、crontab 写入修复 |
src/assets/index.html | 版本号占位符 __INKS_VERSION__ |
🔧 技术亮点
1. POSIX 标准 Shell 转义
pub fn shell_quote(s: &str) -> String { format!("'{}'", s.replace('\'', "'\\''"))}这是 Unix/Linux 中最安全的 shell 转义方式:
- 用单引号包裹整个字符串
- 内部单引号用
'\''拼接(关闭单引号 → 插入转义的单引号 → 重新打开单引号) - 适用于所有 shell(bash、sh、zsh 等)
2. SSH 主机密钥验证(TOFU 策略)
match known_hosts.check(&host_with_port, key) { ssh2::CheckResult::Match => { /* 通过 */ } ssh2::CheckResult::NotFound => { // 首次连接:自动添加 known_hosts.add(...); known_hosts.write_file(...); } ssh2::CheckResult::Mismatch => { // 密钥不匹配:拒绝连接 anyhow::bail!("🚨 主机密钥不匹配!可能是 MITM 攻击"); }}TOFU(Trust On First Use) 策略:
- 首次连接:自动信任并保存密钥
- 后续连接:验证密钥是否匹配
- 密钥变更:拒绝连接并警告用户
3. 智能 EventLoop 调度
if has_pending.load(Ordering::Acquire) { // 有待处理消息:快速响应(60fps) *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(16));} else { // 空闲状态:低功耗(1fps) *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::from_secs(1));}优势:
- 有 IPC 消息时:16ms 轮询,确保 UI 响应流畅
- 空闲时:1s 轮询,大幅降低 CPU 使用率
- 比固定的 60fps 轮询节省约 99% 的 CPU 时间
📈 修复前后对比
安全性
| 维度 | 修复前 | 修复后 |
|---|---|---|
| Shell 注入 | ❌ 20+ 处漏洞 | ✅ 全部转义 |
| MITM 防护 | ❌ 无验证 | ✅ TOFU 策略 |
| Crontab 注入 | ❌ 语法破坏 | ✅ 安全写入 |
| HTML 注入 | ❌ </script> 可注入 | ✅ 转义 <> |
性能
| 维度 | 修复前 | 修复后 |
|---|---|---|
| EventLoop | 60fps 忙轮询 | 智能调度(空闲 1fps) |
| 目录创建 | 逐文件检查 | 批量去重创建 |
| 上传过滤 | 无 | 跳过 .git/node_modules 等 |
可用性
| 维度 | 修复前 | 修复后 |
|---|---|---|
| 构建命令 | 硬编码 npm run build | 可配置 |
| dry-run | 空壳 | 完整模拟 |
| 版本号 | 硬编码 | 自动注入 |
| 配置验证 | GUI 绕过 | validate_for_gui() |
🎯 后续建议
- 单元测试: 为
shell_quote()、verify_host_key()等关键函数编写测试 - 集成测试: 模拟 SSH 连接,测试完整的部署流程
- 错误处理: 统一错误类型,提供更友好的错误提示
- 日志系统: 引入
tracing或logcrate,支持日志级别和输出格式配置 - 配置验证: 增强
validate_for_gui(),检查路径是否存在、端口是否有效等
审计完成时间: 2026-05-29
审计工具: CodeBuddy AI
审计状态: ✅ 全部完成