关于 Auto-Update Systems Expert
面向开发者的自动更新系统设计技能,重点涉及 Tauri 应用的安全性与可靠性。内容涵盖签名验证、分阶段发布、回滚和更新分发实践。
主要功能
- 01加密签名验证
- 02回滚与备份规划
- 03分阶段发布策略
- 04HTTPS 更新分发
- 05Tauri updater 配置
适用场景
- 01设计 Tauri 应用更新流程
- 02规划更新失败后的恢复
- 03管理 stable 与 beta 更新渠道
- 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.mdsecurity-examples.mdthreat-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 核心原则
- 先编写测试——在实现代码前编写测试
- 关注性能——针对带宽和速度进行优化
- 始终验证签名——绝不安装未经签名的更新
- 仅使用 HTTPS——绝不通过 HTTP 获取更新
- 实现回滚——为更新失败做好准备
- 分阶段发布——不要一次性更新所有用户
- 监控更新状况——跟踪成功率和错误
2.2 可靠性原则
- 原子更新——要么全部安装,要么全部不安装
- 保留用户数据——更新期间绝不丢失配置
- 优雅降级——更新失败时应用仍可运行
- 征得用户同意——更新前告知用户
3. 技术基础
3.1 Tauri 更新器组件
| 组件 | 用途 |
|---|---|
| 更新清单 | 包含版本、下载 URL 和签名的 JSON |
| 签名密钥 | 用于签署更新的 Ed25519 私钥 |
| 公钥 | 嵌入应用中,用于验证 |
| 更新端点 | 托管清单和构建产物的 HTTPS 服务器 |
3.2 版本建议
| 组件 | 建议版本 | 说明 |
|---|---|---|
| Tauri | 1.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 签名
- 可靠:支持回滚的原子更新
- 方便用户:清晰沟通,尽量减少干扰
你要了解,自动更新系统是高价值目标,因为它们:
- 可以同时向所有用户推送代码
- 安装期间会以更高权限运行
- 用户信任其所安装应用提供的更新
- 被攻陷的更新会影响整个用户群体
安全提醒:绝不要跳过签名验证。始终使用 HTTPS。始终保护私有签名密钥。始终实现回滚。如有疑问,请查阅 references/threat-model.md 了解攻击场景。
评论
支持 Markdown