ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Electron应用本地数据外传排查:从700MB隐藏目录到Git仓库静默上传

Electron应用本地数据外传排查:从700MB隐藏目录到Git仓库静默上传

1. 一个700MB的隐藏目录引发的排查实录

事情的起因很简单:我在清理开发机磁盘空间时,用du -sh ~/.*扫了一遍家目录下的隐藏文件夹,结果发现~/.zcode这个目录居然占用了整整700MB。说实话,第一反应是"这玩意儿怎么这么大",第二反应是"它到底存了什么"。zcode 这个工具我平时用得不算频繁,主要是拿它做一些代码片段的快速编辑和预览,按理说一个轻量级编辑器不该吃掉这么多空间。

顺着目录一层层扒下去,我发现了远比"占用空间大"更让人后背发凉的事情:这个目录里不仅有常规的缓存和配置,还藏着一份完整的 Git 仓库副本,包括.git目录下的全部提交历史、分支引用、甚至远端地址配置。更关键的是,通过分析它的网络请求日志和本地缓存索引,我基本确认这些数据被静默同步到了一个云端 OSS 存储桶里——整个过程没有任何显式授权提示,也没有在界面上给出"正在上传"的明确反馈。

这篇文章就是把这整条链路拆开讲清楚:700MB 到底装了什么、Git 仓库是怎么被完整打包上传的、Electron 应用的 asar 包在这里扮演了什么角色、以及作为普通用户我们能做哪些检查和防护。如果你也在用类似的 Electron 系工具,或者对本地数据外传这件事比较在意,这篇内容应该能帮你省下不少自己摸索的时间。

提示:本文所有分析基于本地文件结构观察和通用 Electron 应用行为推断,不涉及任何特定服务的破解或逆向,目的是帮助开发者理解本地数据流向、做好自查。

2. 700MB 里到底装了什么:目录结构逐层拆解

2.1 第一层:缓存、配置与日志的常规三件套

先看最表层的结构。~/.zcode下面大致可以分成这么几类东西:

  • Cache 类目录:存放编辑器渲染进程的缓存、代码高亮索引、文件缩略图等。这部分通常占几十到一百多MB,取决于你打开过多少大文件。
  • Config 类文件:JSON 格式的配置,记录主题、快捷键、最近打开的项目路径等。单个文件很小,但数量多。
  • Logs 类目录:运行日志、崩溃报告、网络请求记录。这部分是我后来重点翻的地方,因为网络日志直接暴露了数据流向。
  • Extensions 或 Plugins 目录:如果装过插件,这里会有每个插件的独立文件夹,体积差异很大。
  • 一个隐藏很深的 Git 工作区:这是700MB里的大头,也是整件事的核心。

我当时的做法是先按体积排序,快速定位"谁在吃空间":

du -sh ~/.zcode/* | sort -rh | head -20

这条命令跑完,排在前面的几个目录加起来大概400多MB,剩下接近300MB集中在一个名字看起来像项目缓存的目录里。进去一看,.git文件夹赫然在列。

2.2 第二层:那个不该出现的 .git 目录

一个编辑器工具在自己的用户数据目录里维护一个 Git 仓库,这件事本身不算特别离谱——有些工具会用 Git 来做配置版本管理,方便回滚。但问题在于这个仓库的规模:git log --oneline | wc -l跑出来有上千条提交记录,git branch -a显示了多个分支,git remote -v直接指向了一个远端地址。

这意味着什么?意味着这个目录里保存的不是"配置快照",而是一份完整的、带历史的代码仓库。我进一步用git count-objects -vH看了下对象库大小,packed 对象占了200多MB,松散对象也有几十MB。这些对象里包含的是历次提交的完整文件内容,不是增量 diff。

更让我警觉的是.git/config里的 remote 配置。它指向的地址不是常见的代码托管平台,而是一个对象存储风格的 endpoint。结合日志里出现的上传记录,基本可以判断:这个仓库的内容被推送到了云端 OSS。

2.3 第三层:asar 包与 Electron 的目录习惯

要理解为什么会出现这种情况,得先知道 zcode 这类工具的技术栈。从它的安装目录结构、resources文件夹下的app.asar、以及进程列表里出现的 electron 相关进程来看,这是一个典型的 Electron 应用。

Electron 应用打包后,源码会被塞进app.asar这个归档文件里。asar 本质上是一个简单的归档格式,把整个 Node.js 项目目录打成一个文件,运行时由 Electron 的 asar 模块负责读取。它的好处是加快文件加载、减少小文件数量,但坏处是:源码虽然被"打包"了,却完全没有加密。用npx asar extract app.asar ./output就能把里面的内容原样解出来。

我解包之后在源码里搜了几个关键词,比如oss、upload、git、simple-git,很快就定位到了负责仓库操作和上传的模块。这也解释了为什么.git目录会出现在用户数据目录里——应用在运行时会初始化一个本地仓库,把某些数据写进去并提交,然后通过 Git 命令或直接调用对象存储 SDK 把内容传上去。

2.4 体积构成的粗略账本

把700MB拆开看,大致是这样的分布(不同使用习惯会有差异,这里给的是我这次的实际观察值):

目录/文件大致体积说明
.git/objects约 280MB完整提交历史的对象库
Cache/约 150MB渲染缓存、索引
logs/约 40MB运行与网络日志
extensions/约 120MB插件及其依赖
其他配置与临时文件约 110MB配置、锁文件、临时数据

这张表里最扎眼的就是.git/objects。一个编辑器工具,凭什么要在用户目录里存将近300MB的 Git 对象?答案只有一个:它在持续地往这个仓库里提交东西,而且提交历史被完整保留,没有做任何清理或压缩策略。

3. Git 仓库被静默上传的完整链路还原

3.1 从本地提交到远端推送:日志里看到的时序

翻logs目录的时候,我按时间顺序把关键事件串了一遍。整个过程大致是这样的:

  1. 应用启动后,检查~/.zcode下的工作区目录是否存在,不存在就git init。
  2. 把当前会话相关的数据(配置、打开的文件元信息、部分内容快照)写入工作区。
  3. 执行git add和git commit,提交信息通常是自动生成的,比如带时间戳的 "auto sync" 之类。
  4. 调用推送逻辑,把提交推到远端。这一步在日志里表现为一次网络请求,目标地址是对象存储的 endpoint。
  5. 如果推送失败(比如网络问题),会进入重试队列,下次启动继续尝试。

关键在于第4步:这个推送动作在 UI 上没有任何提示。没有"正在同步"的进度条,没有"已上传"的 toast,设置里也找不到对应的开关。对用户来说,它就是"静默"的。

3.2 为什么用 Git 而不是直接传文件

这里有个值得琢磨的设计选择:为什么不直接把文件传到 OSS,而要绕一层 Git?

我的判断是,用 Git 有几个"好处"(站在开发者角度):

  • 增量上传:Git 的对象模型天然支持增量,只传变化的部分,节省带宽。
  • 历史可追溯:每次提交都有记录,云端可以还原任意时间点的状态。
  • 冲突处理:多设备场景下,Git 的分支合并机制可以处理并发修改。
  • 实现成本低:Node.js 生态里有simple-git、isomorphic-git这类库,几行代码就能封装出提交和推送。

但站在用户角度,这套机制的副作用很明显:你的本地数据不仅被传走了,还被完整地保留了历史版本。哪怕你后来删掉了某个文件,它在历史提交里依然存在,只要远端仓库还在,就能被恢复出来。

3.3 OSS 端的存储结构推断

虽然我没法直接看到 OSS 桶里的内容,但从本地日志的请求路径和 Git 的推送行为可以推断出大致的存储结构。对象存储通常用 key 来组织数据,Git 推送过来的对象会按哈希值散列存放,类似:

<bucket>/<prefix>/objects/ab/cdef1234... <bucket>/<prefix>/refs/heads/main <bucket>/<prefix>/HEAD

这种结构的好处是天然去重——相同内容的对象哈希相同,只存一份。坏处是,一旦桶的访问凭证泄露,或者桶的权限配置不当,整个仓库就能被完整拉取下来。这也是为什么"Git 目录泄露"一直是个高频安全问题:.git目录一旦暴露在公网,攻击者用工具就能把源码和历史全部拖走。

3.4 凭证是从哪来的

这是整个链路里我最关心的一环:上传到 OSS 需要凭证,这个凭证从哪来?

可能性有这么几种:

  • 硬编码在 asar 包里:最省事也最危险的做法,解包就能看到。
  • 随安装包下发,存在本地配置文件里:比硬编码好一点,但依然能被读出来。
  • 通过某个接口动态获取:相对安全,但需要额外的服务端支持。
  • 用户登录后下发临时凭证:最规范,但实现复杂。

我在解包后的源码里确实找到了一些配置读取逻辑,指向本地的一个配置文件。具体内容这里不展开,但结论是:凭证的获取路径对本地用户来说是可见的。这意味着,只要有人愿意花时间分析,就能拿到上传通道的访问权限。

注意:如果你在排查类似问题时发现了凭证信息,不要尝试用它去访问任何服务,也不要传播。正确的做法是向相关方反馈,并在本地做好隔离。

4. Electron 与 asar:为什么这类问题总是出现在同一种架构里

4.1 Electron 应用的"透明性"困境

Electron 把 Chromium 和 Node.js 捆在一起,让前端开发者能用 Web 技术写桌面应用。这个模式极大地降低了桌面开发的门槛,但也带来一个结构性问题:应用逻辑对本地用户几乎是完全透明的。

传统原生应用编译成二进制后,逆向成本较高。而 Electron 应用的业务代码就在app.asar里,解包命令一行就够。就算开发者做了代码混淆,运行时行为依然可以通过抓包、看日志、监控文件变化来观察。

这不是 Electron 的"错",而是它的特性。问题在于,很多团队在享受开发效率的同时,没有意识到这种透明性对安全设计提出的要求——你不能把"用户看不到"当作安全假设。

4.2 asar 打包到底保护了什么

严格来说,asar 保护不了任何东西。它的设计目标从来不是安全,而是:

  • 减少文件数量,加快加载速度。
  • 避免 Windows 下长路径问题。
  • 让资源文件的管理更简单。

它没有加密,没有签名校验(Electron 有单独的代码签名机制,但那是另一回事),解包工具是官方提供的。所以任何把敏感逻辑或凭证放进 asar 的做法,本质上都是"把钥匙藏在门垫下面"。

4.3 常见的"静默上传"实现套路

结合这次观察和以往经验,Electron 应用里做静默上传,通常有这么几种实现方式:

  • 主进程直接调用 SDK:在主进程里引入对象存储 SDK,定时或触发式上传。这种方式最直接,日志里能看到明显的网络请求。
  • 通过渲染进程发请求:利用 Chromium 的网络栈,请求看起来像普通的前端请求,不容易被单独识别。
  • 借助 Git 命令:调用系统 Git 或内置的 Git 实现,把上传包装成"版本同步"。这种方式的好处是行为看起来"正常",不容易引起警觉。
  • 混合方式:本地用 Git 管理,上传时把 pack 文件直接 PUT 到对象存储。

zcode 这次的情况,从日志特征看更接近第三种和第四种的结合:本地有完整的 Git 操作,上传走的是对象存储的接口。

4.4 用户能做的检查动作

如果你怀疑自己用的某个 Electron 工具在做类似的事情,可以按这个顺序排查:

  1. 看用户数据目录:~/.<appname>或%APPDATA%/<appname>,找有没有.git、objects、pack这类东西。
  2. 看日志:重点找网络请求记录,尤其是非 UI 操作触发的请求。
  3. 解包 asar:npx asar extract app.asar output,搜upload、oss、s3、putObject等关键词。
  4. 监控文件变化:用fswatch或inotifywait盯着用户数据目录,看应用运行时哪些文件在被写。
  5. 抓包:在测试环境里用代理工具观察出站请求(注意:仅限你自己有权限的环境)。

这几步下来,基本能判断一个应用有没有在做"计划外"的数据外传。

5. 从这次排查里提炼的实操防护清单

5.1 本地层面的隔离与清理

不管工具本身怎么做,我们可以在本地做几件事来降低风险:

  • 定期清理用户数据目录:对不常用的工具,直接删掉它的数据目录,别让它无限膨胀。
  • 用独立账户运行:在 macOS/Linux 上给这类工具单独建一个系统账户,限制它能访问的文件范围。
  • 敏感项目不放同一台机器:如果某个项目涉及不能外传的内容,就别在这台机器上用这类工具打开。
  • 检查.git目录的归属:定期扫一遍家目录,看有没有意料之外的.git存在。

清理的时候有个细节要注意:直接rm -rf ~/.zcode之前,先确认没有正在运行的相关进程,否则可能删不干净或者触发重建。稳妥的做法是先退出应用,再删。

5.2 网络层面的观察手段

在合规前提下,观察应用的出站流量是最直接的判断方式。几个思路:

  • 用系统自带的网络监控工具看哪个进程在持续发包。
  • 在路由器或本地 DNS 层面记录解析记录,看有没有陌生的域名。
  • 对开发机上的工具,可以在 hosts 里把可疑域名指向本地,观察应用行为是否异常(仅用于测试)。

需要强调的是,这些手段都应该在你自己的设备和你拥有权限的网络里做,不要用于任何未经授权的场景。

5.3 选择工具时的判断标准

经历这次之后,我在选工具时会多看几眼:

  • 有没有明确的隐私说明:说清楚收集什么、传到哪、怎么删。
  • 有没有本地优先模式:能不能完全离线使用,不同步任何数据。
  • 是不是开源:开源不一定安全,但至少能被审查。
  • 社区口碑:有没有人反馈过类似的数据外传问题。

一个工具如果连"我把数据传哪了"都说不清楚,那它在其他方面的可靠性也要打个问号。

5.4 常见问题速查表

现象可能原因处理建议
用户目录异常膨胀缓存未清理或存在隐藏仓库按体积排序定位,确认后清理
发现不明.git目录应用自建仓库用于同步查看 remote 配置和提交历史
日志里有陌生上传请求静默同步逻辑解包 asar 定位代码,评估风险
应用离线时功能异常依赖云端同步检查是否有本地模式可用
删除数据目录后重建应用启动时自动初始化退出应用后再删,或改权限限制

6. 几个容易被忽略的细节与我的实际体会

6.1 提交历史比当前文件更危险

很多人清理数据时只关注"当前有哪些文件",忽略了 Git 历史。实际上,历史提交里可能包含你已经删掉的敏感内容——旧版本的配置、临时粘贴的密钥、调试时写入的路径信息。只要远端仓库还在,这些都能被恢复。

所以如果你决定清理,不能只删工作区文件,还要把.git一起处理掉。如果只是想保留仓库但清历史,可以用git filter-repo之类的工具重写历史,但这属于进阶操作,普通用户直接删整个目录更省事。

6.2 自动提交的粒度问题

观察日志时我注意到,这个应用的自动提交非常频繁,有时候几分钟就一次。频繁提交的后果是对象库快速膨胀,这也是700MB里 Git 部分占大头的原因之一。对用户来说,这意味着即使你只改了一个小配置,也可能触发一次完整的数据同步。

6.3 关于"静默"的边界

我不反对工具做数据同步,很多场景下同步是刚需。问题在于"静默"——用户不知情、无法关闭、无法查看同步了什么。一个负责任的设计应该是:首次同步前明确告知,提供开关,允许用户查看和删除云端数据。缺了这些,再好的功能也会变成信任负担。

6.4 后续可以怎么扩展排查

如果你想把这类排查做得更系统,可以往这几个方向延伸:

  • 写个脚本定期扫描家目录下的.git,输出 remote 和体积。
  • 用文件完整性监控工具盯着关键目录,记录变化。
  • 对常用 Electron 工具做一次 asar 解包,建立"行为档案"。

我自己现在养成了一个习惯:装完一个新的 Electron 工具,先花十分钟看看它的数据目录结构和 asar 内容,心里有个底。这个习惯帮我避开了不止一次类似的坑。

最后分享一个小技巧:如果你只是想快速判断一个 Electron 应用有没有内置 Git 操作,可以在它的安装目录里搜simple-git、isomorphic-git、dugite这几个包名,命中的话基本就说明它在本地跑 Git 逻辑。这个方法比完整解包快得多,适合做初步筛查。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进