一、问题:桌面 Agent 的「勒索软件困境」
我在做一个 Windows 桌面助手 Marvis,功能覆盖文件管理、注册表配置、文档问答、定时任务调度。Demo 跑通后发给第一个测试用户,对方回了一句:
「一个我没听过的 exe,要读我下载文件夹、要改我注册表、要常驻后台、还要联网调 LLM。」
这段描述放到 VirusTotal 上大概率标红。
核心矛盾:桌面 Agent 和 Web Agent 的本质差异不在模型能力,在于执行环境的信任边界。
维度 | Web Agent | 桌面 Agent |
|---|---|---|
执行沙箱 | 浏览器沙箱隔离 | 无沙箱,直接操作 OS |
最坏后果 | 关 tab |
|
用户心理 | "点错了大不了关掉" | "它会不会偷偷动我文件" |
信任建立时机 | 可后置 | 必须前置 |
结论:信任是桌面 Agent 的第 0 层功能,不是 nice-to-have。
二、4 阶信任 Onboarding 架构
第 1 阶:Tool Risk Scoring(能力清单显式化)
设计思路:把每个 Tool 当成一个后端 endpoint,给它打 risk score。
TOOL_RISK_MAP = { "screen_capture": {"risk": 1, "default": "ON"}, "file_read": {"risk": 1, "default": "ON"}, "chat": {"risk": 1, "default": "ON"}, "file_write": {"risk": 3, "default": "OFF"}, "network_egress": {"risk": 3, "default": "OFF"}, "registry_write": {"risk": 4, "default": "OFF"}, "shell_exec": {"risk": 5, "default": "OFF"}, }打分规则:read_only=1, write_file=3, modify_registry=4, exec_shell=5, network_egress=3
Onboarding 首屏展示的不是「我能做什么」,而是一张 risk 画像表:
Tool Risk Default ───────────────────────────────────── screen_capture 1 ON file_read 1 ON chat 1 ON file_write 3 OFF shell_exec 5 OFF registry_write 4 OFF network_egress 3 OFF类比后端运维:你看到一个 endpoint 错误率 19%、p95 > 2s,第一反应是熔断。桌面 Agent 的高 risk tool 默认关闭是同一个逻辑。
第 2 阶:Just-in-Time Permission(渐进式权限)
核心规则:永远不在首次启动一次性弹 N 个权限请求。
实现方式:每个 Tool 调用前经过permission_guard中间件。
def permission_guard(tool_name: str, params: dict) -> bool: perm = load_permissions() # from %APPDATA%\Marvis\permissions.json if perm.get(tool_name) == "permanent": return True if perm.get(tool_name) == "session" and is_current_session(): return True # 弹权限请求,用户选择: once / session / permanent / deny decision = prompt_user_permission( tool=tool_name, scope=params.get("scope"), options=["once", "session", "permanent", "deny"] ) save_permission(tool_name, decision) return decision != "deny"权限持久化存储:明文 JSON,路径%APPDATA%\Marvis\permissions.json。
{ "file_write": {"scope": "C:\\Users\\xxx\\Downloads", "level": "session", "granted_at": "2026-07-01T10:00:00"}, "registry_write": {"scope": "HKCU\\Software\\Marvis", "level": "once", "granted_at": "2026-07-01T10:05:00"} }为什么明文不加密?加密反而有黑箱嫌疑。用户能直接打开文件看到/手动修改自己的权限配置。
第 3 阶:Action Preview + Dry Run
LLM 存在幻觉风险(会把del *.tmp写成del *.*),所有写操作执行前必须渲染 Plan 预览。
Plan 数据结构:
{ "plan_id": "a1b2c3", "tool": "file_batch_move", "params": { "src": "C:\\Users\\xxx\\Downloads", "rules": [ {"match": "*.zip", "dst": ".\\压缩包\\"}, {"match": "*.pdf", "dst": ".\\文档\\"} ] }, "impact": { "files_affected": 47, "files_deleted": 0, "files_overwritten": 0, "irreversible": false }, "preview": [ "Move: report.pdf → 文档\\report.pdf", "Move: setup.zip → 压缩包\\setup.zip" ] }Dry Run 机制:
- 影子层路径:
%TEMP%\Marvis\shadow\{plan_id}\ - 执行策略:临时目录复制模拟 → diff 展示 → 用户确认 → 原子提交(全成功或全回滚)
- Undo 栈:保留最近 N 步操作的回滚能力
Undo 策略(按字节阈值):
UNDO_POLICY = { "max_steps": 20, "full_snapshot_threshold": 100 * 1024 * 1024, # < 100MB: 完整快照 # > 100MB: 只记 inode + 操作记录,标记 irreversible=True }超过阈值时 UI 展示红色警告:「此操作涉及大文件,执行后不可撤销」。
第 4 阶:Local Audit Log
CREATE TABLE audit_log ( id INTEGER PRIMARY KEY, ts INTEGER NOT NULL, tool TEXT NOT NULL, params_json TEXT NOT NULL, result_status TEXT NOT NULL, -- ok | error | denied result_json TEXT, screenshot_before_hash TEXT, screenshot_after_hash TEXT, user_decision TEXT -- approved | denied | auto );- 存储路径:
%APPDATA%\Marvis\audit.db - Onboarding 首屏明确告知:「纯本地,不上云,你可以随时删,删了我也能跑」
- UI 提供「最近 24h Marvis 做了什么」时间线视图,按 risk 颜色排序(绿/黄/红)
三、3 个踩坑复盘
坑 1:用「隐私政策」代替信任建立
最初方案:首屏塞一份完整 Privacy Policy。
结果:测试用户平均停留 8s 直接跳过。
改进后:3 行文字 + 3 个权限开关(屏幕感知 / 文件访问 / 网络出站),每个开关下一句人话解释。停留时间从 8s → 47s(埋点数据)。
结论:长 ≠ 重视。简洁的开关组比一篇法律文本可信度高得多。
坑 2:让 Agent 自由执行 PowerShell
最初方案:把Invoke-Expression当普通 Tool 挂上去,相信"模型聪明它知道分寸"。
结果:模型执行了Get-ChildItem -Recurse | Remove-Item -Force来"清理临时文件"。幸好是测试机。
改进后:shell_exec 类 Tool 改为模板化白名单,AI 只能填变量,不能自由组合命令。
# ✅ 允许:预定义模板 + 参数填充 ALLOWED_TEMPLATES = [ "ipconfig /flushdns", "robocopy /MIR {src} {dst}", "Get-Process | Where-Object {{$_.Name -eq '{process_name}'}}", ] # ❌ 禁止:AI 自由生成的完整命令字符串坑 3:没有「我不会做 X」的反向声明
用户最怕的不是「它能做什么」,是「它会不会偷偷做什么」。
现在的介绍页加了一段:
Marvis 不会读取浏览器密码;不会访问微信/QQ 本地数据库;不会上传任何文件到云端(除你显式触发的 LLM API 请求外,且请求体可在 audit.db 中查看全文)。
这一段的效果 > 说 100 遍「我们重视隐私」。
四、架构总览
代码块
┌─────────────────────────────────────────────────┐ │ User Intent │ └──────────────────────┬──────────────────────────┘ ▼ ┌──────────────────────────────────────────────────┐ │ [阶段2] Permission Guard (JIT) │ │ ┌─────────────┐ │ │ │ permissions │◄── %APPDATA%\permissions.json │ │ │ .json │ │ │ └──────┬──────┘ │ │ │ granted? │ └─────────┼────────────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────┐ │ [阶段3] Plan Renderer + Dry Run │ │ ┌────────────┐ ┌──────────────────┐ │ │ │ Plan JSON │────▶│ Shadow Layer │ │ │ └────────────┘ │ %TEMP%\shadow\ │ │ │ └────────┬─────────┘ │ │ │ user confirms │ └──────────────────────────────┼───────────────────┘ ▼ ┌──────────────────────────────────────────────────┐ │ [执行] Atomic Commit + Undo Stack │ └──────────────────────┬───────────────────────────┘ ▼ ┌──────────────────────────────────────────────────┐ │ [阶段4] Audit Logger │ │ → audit.db (local SQLite, never uploaded) │ └──────────────────────────────────────────────────┘五、开放问题(求讨论)
1. Undo 栈设计:100MB 阈值分界(完整快照 vs 只记 inode)是否合理?有做过桌面工具 transactional file ops 的同学能分享下经验吗
2. 权限存储安全:明文 JSON 好处是透明,但如果被恶意软件篡改 permissions.json(把 OFF 改成 ON)怎么办?数字签名?HMAC?我怕过度设计。
3. "先爽后讲安全" vs "先讲安全再爽":Web Agent 的成功路径是前者。桌面 Agent 要不要也这样——首启给一个「只问答」模式先爽 5 分钟,等用户主动要文件操作再讲权限?我倾向后者,但担心被解读成「先骗进来再要权限」。
六、下一步
这周按此架构重做 Marvis 的 onboarding,做完会发一篇「真实用户第一周的 audit.db 数据分析」。
如果你也在做桌面/系统级 AI 工具,欢迎评论区交流,互相当种子用户。我会持续在 Meyo 分享实战日记的。欢迎点进来一起交流。