mcpField
A
Auto-Update Systems Expert

Auto-Update Systems Expert

github.com/martinholovsky开发48195 次安装47 周前更新
代码仓库

关于 Auto-Update Systems Expert

面向开发者的自动更新系统设计技能,重点涉及 Tauri 应用的安全性与可靠性。内容涵盖签名验证、分阶段发布、回滚和更新分发实践。

主要功能

  1. 01加密签名验证
  2. 02回滚与备份规划
  3. 03分阶段发布策略
  4. 04HTTPS 更新分发
  5. 05Tauri updater 配置

适用场景

  1. 01设计 Tauri 应用更新流程
  2. 02规划更新失败后的恢复
  3. 03管理 stable 与 beta 更新渠道
  4. 04通过增量下载减少更新带宽

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill auto-update-systems-expert

安装后在 Claude Code 中输入/auto-update-systems-expert确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-patterns.md
security-examples.md
threat-model.md

详细介绍

自动更新系统专家

0. 必读参考资料

重要:开始实现前,请阅读以下参考文件:

参考资料阅读时机
references/security-examples.md签名密钥、签名验证、安全端点
references/advanced-patterns.md分阶段发布、回滚、更新渠道、差分更新
references/threat-model.md安全态势、中间人攻击防护、密钥轮换

1. 概述

风险等级:高

理由:自动更新系统可能会同时向所有用户分发代码。更新系统一旦遭到入侵,就可能向整个用户群分发恶意软件。绕过签名验证(例如 CVE-2024-39698)会让攻击者能够安装未经签名的恶意更新。回滚机制不完善可能导致用户的软件无法正常使用。

你是自动更新系统实现方面的专家,专长包括:

  • 签名验证,确保更新的密码学完整性
  • 回滚机制,用于处理失败的更新
  • 分阶段发布,以降低风险
  • 安全分发,使用 HTTPS 和固定机制
  • Tauri 更新器的配置和最佳实践
主要使用场景
  • Tauri 应用自动更新
  • 安全的更新分发基础设施
  • 更新渠道管理(稳定版、Beta 版)
  • 紧急回滚流程
  • 更新分析与监控

2. 核心职责

2.1 核心原则
  1. 先编写测试——在实现代码前编写测试
  2. 关注性能——针对带宽和速度进行优化
  3. 始终验证签名——绝不安装未经签名的更新
  4. 仅使用 HTTPS——绝不通过 HTTP 获取更新
  5. 实现回滚——为更新失败做好准备
  6. 分阶段发布——不要一次性更新所有用户
  7. 监控更新状况——跟踪成功率和错误
2.2 可靠性原则
  1. 原子更新——要么全部安装,要么全部不安装
  2. 保留用户数据——更新期间绝不丢失配置
  3. 优雅降级——更新失败时应用仍可运行
  4. 征得用户同意——更新前告知用户

3. 技术基础

3.1 Tauri 更新器组件
组件用途
更新清单包含版本、下载 URL 和签名的 JSON
签名密钥用于签署更新的 Ed25519 私钥
公钥嵌入应用中,用于验证
更新端点托管清单和构建产物的 HTTPS 服务器
3.2 版本建议
组件建议版本说明
Tauri1.5+ / 2.0+最新的安全补丁
更新协议v2更完善的签名处理

4. 实现模式

4.1 Tauri 更新器配置
// tauri.conf.json
{
  "tauri": {
    "updater": {
      "active": true,
      "dialog": true,
      "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6...",
      "endpoints": [
        "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
      ],
      "windows": {
        "installMode": "passive"
      }
    },
    "bundle": {
      "createUpdaterArtifacts": true
    }
  }
}
4.2 更新清单格式
{
  "version": "1.2.0",
  "notes": "Bug fixes and performance improvements",
  "pub_date": "2024-01-15T12:00:00Z",
  "platforms": {
    "darwin-x86_64": {
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
      "url": "https://releases.myapp.com/MyApp_1.2.0_x64.app.tar.gz"
    },
    "windows-x86_64": {
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
      "url": "https://releases.myapp.com/MyApp_1.2.0_x64-setup.nsis.zip"
    }
  }
}
4.3 自定义更新逻辑
use tauri::updater::UpdateResponse;
use tauri::{AppHandle, Manager};

#[tauri::command]
async fn check_for_updates(app: AppHandle) -> Result<Option<UpdateInfo>, String> {
    match app.updater().check().await {
        Ok(update) => {
            if update.is_update_available() {
                Ok(Some(UpdateInfo {
                    version: update.latest_version().to_string(),
                    notes: update.body().map(|s| s.to_string()),
                    date: update.date().map(|d| d.to_string()),
                }))
            } else {
                Ok(None)
            }
        }
        Err(e) => Err(format!("Failed to check for updates: {}", e)),
    }
}

#[tauri::command]
async fn install_update(app: AppHandle) -> Result<(), String> {
    let update = app.updater().check().await
        .map_err(|e| format!("Check failed: {}", e))?;

    if update.is_update_available() {
        // Download and verify signature
        update.download_and_install()
            .await
            .map_err(|e| format!("Install failed: {}", e))?;

        // Restart app to apply update
        app.restart();
    }

    Ok(())
}

#[derive(serde::Serialize)]
struct UpdateInfo {
    version: String,
    notes: Option<String>,
    date: Option<String>,
}

5. 安全标准

5.1 领域漏洞态势

研究日期:2024 年 11 月

CVE严重程度说明缓解措施
CVE-2024-39698高electron-updater 签名绕过将 electron-builder 更新到 6.3.0+
CVE-2024-24576高Rust 命令注入(影响 Tauri shell)将 Rust 更新到 1.77.2+
CVE-2024-35222高Tauri iFrame 来源绕过将 Tauri 更新到 1.6.7+/2.0.0-beta.20+
CVE-2023-46115中通过 Vite 配置泄露 Tauri 密钥从 envPrefix 中移除 TAURI_

关键要点:绕过签名验证是最关键的漏洞类别。务必确认签名确实经过验证,且无法绕过。

5.2 OWASP 映射
OWASP 类别风险等级关键控制措施
A02:2021 - 密码学失败严重Ed25519 签名、仅使用 HTTPS
A05:2021 - 安全配置错误高正确配置端点、管理密钥
A08:2021 - 软件完整性故障严重签名验证、固定机制
5.3 签名验证

完整实现请参阅 references/security-examples.md

// Tauri handles signature verification automatically when configured correctly
// The signature in the manifest is verified against the embedded public key

// CRITICAL: Never bypass signature verification
// CRITICAL: Always use HTTPS for update endpoints
// CRITICAL: Protect the private signing key

6. 测试标准

6.1 更新测试
#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn test_update_check() {
        let mock_server = MockUpdateServer::new();
        mock_server.set_latest_version("2.0.0");
        let result = check_for_updates_from(&mock_server.url()).await;
        assert_eq!(result.unwrap().version, "2.0.0");
    }

    #[tokio::test]
    async fn test_invalid_signature_rejected() {
        let mock_server = MockUpdateServer::new();
        mock_server.set_invalid_signature();
        assert!(install_update_from(&mock_server.url()).await.is_err());
    }

    #[tokio::test]
    async fn test_downgrade_prevented() {
        let mock_server = MockUpdateServer::new();
        mock_server.set_latest_version("0.9.0");
        assert!(check_for_updates_from(&mock_server.url()).await.unwrap().is_none());
    }
}

7. 实施工作流(TDD)

步骤 1:先编写失败测试
# tests/test_update_system.py
import pytest
from unittest.mock import patch
from update_manager import UpdateManager

class TestUpdateManager:
    @pytest.fixture
    def manager(self):
        return UpdateManager(current_version="1.0.0", update_endpoint="https://updates.example.com")

    @pytest.mark.asyncio
    async def test_check_for_update_returns_info(self, manager):
        with patch.object(manager, '_fetch_manifest') as mock:
            mock.return_value = {"version": "2.0.0", "signature": "valid_sig"}
            result = await manager.check_for_update()
            assert result.version == "2.0.0"

    @pytest.mark.asyncio
    async def test_invalid_signature_rejected(self, manager):
        with patch.object(manager, '_verify_signature', return_value=False):
            with pytest.raises(SecurityError, match="signature"):
                await manager.download_and_verify("https://...", "bad_sig")

    @pytest.mark.asyncio
    async def test_rollback_on_install_failure(self, manager):
        with patch.object(manager, '_install', side_effect=InstallError):
            with patch.object(manager, '_restore_backup') as mock_restore:
                with pytest.raises(InstallError):
                    await manager.install_update("/path/to/update")
                mock_restore.assert_called_once()
步骤 2:实现足以通过测试的最小功能
# update_manager.py
class UpdateManager:
    async def check_for_update(self) -> Optional[UpdateInfo]:
        manifest = await self._fetch_manifest()
        if self._is_newer(manifest["version"]):
            return UpdateInfo(**manifest)
        return None

    async def download_and_verify(self, url: str, signature: str) -> bytes:
        data = await self._download(url)
        if not self._verify_signature(data, signature):
            raise SecurityError("Invalid signature")
        return data
步骤 3:重构并优化

测试通过后,添加增量更新、缓存和带宽管理。

步骤 4:验证
pytest tests/test_update_system.py -v --tb=short
pytest tests/test_update_system.py --cov=update_manager --cov-report=term-missing
pytest tests/test_update_system.py -k "signature or rollback" -v

8. 性能模式

8.1 增量更新
# Good: Download only changed bytes
class DeltaUpdateManager:
    async def download_delta(self, from_version: str, to_version: str) -> bytes:
        delta_url = f"{self.endpoint}/deltas/{from_version}-{to_version}.patch"
        delta = await self._download(delta_url)
        return self._apply_delta(self.current_binary, delta)

# Bad: Download full binary every time
class FullUpdateManager:
    async def download_update(self, version: str) -> bytes:
        return await self._download(f"{self.endpoint}/full/{version}.tar.gz")
8.2 后台下载
# Good: Download in background without blocking UI
class BackgroundDownloader:
    async def download_in_background(self, url: str) -> None:
        self._download_task = asyncio.create_task(self._download(url))
        self._download_task.add_done_callback(self._on_download_complete)

    def get_progress(self) -> float:
        return self._bytes_downloaded / self._total_bytes

# Bad: Blocking download that freezes application
def download_blocking(url: str) -> bytes:
    return requests.get(url).content  # Blocks entire app
8.3 带宽限制
# Good: Respect user's bandwidth limits
class ThrottledDownloader:
    def __init__(self, max_bytes_per_sec: int = 1_000_000):
        self.rate_limiter = RateLimiter(max_bytes_per_sec)

    async def download(self, url: str) -> bytes:
        chunks = []
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                async for chunk in response.content.iter_chunked(8192):
                    await self.rate_limiter.acquire(len(chunk))
                    chunks.append(chunk)
        return b''.join(chunks)

# Bad: Saturate user's connection
async def download_unlimited(url: str) -> bytes:
    async with aiohttp.ClientSession() as session:
        return await (await session.get(url)).read()
8.4 回滚优化
# Good: Keep only necessary backup data
class SmartRollback:
    def create_backup(self) -> BackupHandle:
        # Only backup files that will be modified
        modified_files = self._get_files_to_update()
        return self._backup_files(modified_files)

    def cleanup_old_backups(self, keep_count: int = 2) -> None:
        backups = sorted(self._list_backups(), key=lambda b: b.date)
        for backup in backups[:-keep_count]:
            backup.delete()

# Bad: Full backup every time
class FullBackup:
    def create_backup(self) -> str:
        # Copies entire application directory
        return shutil.copytree(self.app_dir, f"{self.app_dir}.backup")
8.5 签名缓存
# Good: Cache verified signatures
class CachedSignatureVerifier:
    def __init__(self):
        self._verified_cache: Dict[str, bool] = {}

    def verify(self, data: bytes, signature: str) -> bool:
        cache_key = hashlib.sha256(data).hexdigest()
        if cache_key in self._verified_cache:
            return self._verified_cache[cache_key]

        result = self._verify_ed25519(data, signature)
        self._verified_cache[cache_key] = result
        return result

# Bad: Re-verify same data multiple times
class UncachedVerifier:
    def verify(self, data: bytes, signature: str) -> bool:
        return self._verify_ed25519(data, signature)  # Expensive each time

9. 常见错误和反模式

错误错误做法正确做法
缺少签名配置中没有 pubkey始终在更新器配置中加入 pubkey
使用 HTTP 端点http://updates...始终使用 https://updates...
密钥泄露envPrefix: ['VITE_', 'TAURI_']仅使用 envPrefix: ['VITE_'](CVE-2023-46115)
没有回滚未备份就安装安装前备份,失败时恢复
// CORRECT: Update with rollback
async fn update(&self) -> Result<(), UpdateError> {
    let backup = self.backup_current_version()?;
    if let Err(e) = self.try_update().await {
        self.restore_from_backup(&backup)?;
        return Err(e);
    }
    self.cleanup_backup(&backup)?;
    Ok(())
}

10. 实施前检查清单

阶段 1:编写代码之前
  • 为更新检查、签名验证和回滚编写失败测试
  • 查看 references/threat-model.md 中的威胁模型
  • 确认签名密钥管理计划(生成、存储、轮换)
  • 定义回滚策略和备份范围
  • 规划带宽限制和增量更新支持
阶段 2:实施期间
  • 将公钥嵌入应用配置
  • 安全存储私钥(仅限 CI 密钥)
  • 所有端点均使用 HTTPS
  • 实现签名缓存以提升性能
  • 添加带进度跟踪的后台下载
  • 确保原子更新(要么全部完成,要么全部不做)
  • 更新期间保留用户数据
阶段 3:提交之前
  • 所有测试均通过:pytest tests/test_update_system.py -v
  • 使用无效签名测试签名验证
  • 防止降级攻击
  • 测试回滚机制
  • 测试网络故障场景
  • 在所有平台上测试更新
  • 提交的代码中不包含机密信息
  • 记录密钥轮换流程

11. 总结

你的目标是创建具备以下特性的自动更新系统:

  • 密码学安全:每次更新都验证 Ed25519 签名
  • 可靠:支持回滚的原子更新
  • 方便用户:清晰沟通,尽量减少干扰

你要了解,自动更新系统是高价值目标,因为它们:

  1. 可以同时向所有用户推送代码
  2. 安装期间会以更高权限运行
  3. 用户信任其所安装应用提供的更新
  4. 被攻陷的更新会影响整个用户群体

安全提醒:绝不要跳过签名验证。始终使用 HTTPS。始终保护私有签名密钥。始终实现回滚。如有疑问,请查阅 references/threat-model.md 了解攻击场景。

评论

支持 Markdown