给 Electron Agent 加截图取证时,我会先把授权和脱敏账本拆开
最近在做一个 Electron Agent 调试助手,用户遇到页面报错时,可以把当前窗口截图交给模型一起分析。这个能力听起来很小,真正落地时却很容易变成隐私黑盒:谁触发了采集,采到了哪个窗口,上传前有没有裁剪和打码,失败后临时文件有没有被清掉,都需要有证据可查。

原创示意图:把截图取证拆成授权票据、来源选择、裁剪脱敏、轨道释放和留存账本,避免调试图片脱离审计链路。 来源:Codex image generation
问题背景
Electron 的 desktopCapturer 文档说明,它可以访问可用于桌面音视频捕获的媒体源,getSources 能返回屏幕或窗口来源。Electron 的 session.setDisplayMediaRequestHandler 又允许主进程响应来自 navigator.mediaDevices.getDisplayMedia 的显示媒体请求,并能看到 securityOrigin、videoRequested 和 userGesture 等字段。MDN 对 getDisplayMedia 的安全说明更明确:授权不能被持久化复用,每次都需要提示用户,调用还要求临时用户激活。
这几个约束放在 Agent 产品里,意味着截图取证不能被做成后台自动动作。它应该是一条短时、可见、可撤销的链路,用户点下按钮后才开始,结束后马上释放媒体轨道,并留下足够轻的审计记录。
踩坑和关键难点
我最初只在 renderer 里封装了一个 captureCurrentScreen(),然后把图片交给上传服务。小范围调试能跑,接入真实工作台后问题马上出现。第一,renderer 能拿到的能力太宽,后续页面只要误调就可能发起采集。第二,窗口名和截图文件没有同一份 captureJobId,客服看到了图片,却查不到用户当时确认的是哪个来源。第三,脱敏发生在上传后,原图已经离开本机,风险点被推迟到了服务端。第四,用户关闭面板后没有统一调用 MediaStreamTrack.stop(),系统层的共享提示会残留一段时间。
解决思路
我把链路拆成五个账本。授权票据记录 captureJobId、触发入口、路由和过期时间。来源授权由主进程处理,只接受自家 origin 和带用户手势的请求。帧处理只截一帧,先裁剪到用户框选区域。脱敏记录保存 mask 版本和规则命中数量。留存账本只保存图片 hash、尺寸、关联问题单和 retentionUntil,窗口标题这类可能含个人信息的字段只保留归一化类型和 hash。
preload 只通过 contextBridge 暴露一个很窄的 requestEvidenceCapture(),不把完整 ipcRenderer 透给页面。Electron 文档也提醒,直接暴露整块 IPC 能力会让任意代码发送消息,是安全隐患。主进程里再用 setDisplayMediaRequestHandler 做来源选择和兜底拒绝,renderer 只负责用户确认、局部裁剪和本地打码。
关键步骤
第一步,用户点击“添加截图证据”时创建授权票据,票据默认六十秒过期。票据里只放业务必要字段,比如 captureJobId、issueId、routeId 和预期窗口类型。
第二步,主进程处理显示媒体请求。没有用户手势、origin 不匹配、票据过期或请求音频时,直接拒绝。通过时只允许窗口或屏幕来源,并把被授权的来源类型写入账本。
第三步,renderer 从 stream 取一帧,画到 canvas 后立即进入裁剪和脱敏步骤。脱敏在本机完成,原始帧只停留在内存里,不进入上传队列。上传对象用 captureJobId 命名,并附上 mask 版本,方便后续复核。
第四步,清理媒体轨道。MDN 的 MediaStreamTrack.stop() 文档说明,调用后 track 会进入 ended 状态。我在成功、失败、取消和面板卸载四个路径都遍历 stream.getTracks(),逐个 stop,并把 video 的 srcObject 置空。
第五步,给证据设置留存策略。调试类截图默认短期保存,用户提交工单后再转为工单附件。定时任务只看 retentionUntil,不关心业务状态,避免图片被某个失败流程长期挂住。
可复用经验
这类能力的核心是把“能截图”拆成“为什么截图、截哪里、传什么、留多久、谁能查”。Agent 需要上下文,但桌面端的上下文经常包含聊天、文件名、账号和内部系统页面。把授权、裁剪、脱敏、上传和留存分开以后,模型拿到的证据更干净,用户也能清楚撤销哪一份材料。
同一套结构还能复用到浏览器 trace 截图、RAG 文档解析失败快照、QA 缺陷上报和自动化巡检截图。只要让每张图都带上 captureJobId、hash、maskVersion 和过期时间,后续排查就不会陷入“图片从哪里来”的追问里。