Wechatsync 安全审计报告

审计范围 :
@wechatsync/core,@wechatsync/extension,@wechatsync/mcp-server,@wechatsync/cli审计日期 : 2026-07-24
审计目标: 账号窃取风险、未授权数据上传风险
目录
- 项目概述
- 认证机制分析
- 数据流分析
- 账号窃取风险评估
- 未授权数据上传风险评估
- 外部通信审计
- 权限审计
- [API 注入面审计](#API 注入面审计)
- [CMS 凭证存储审计](#CMS 凭证存储审计)
- 综合评分
- 安全加固建议
1. 项目概述
文章同步助手 (Wechatsync) 是一款开源免费的 Chrome 浏览器扩展,用于将文章一键同步到知乎、掘金、CSDN、头条等 29+ 平台。
架构组件
bash
┌─────────────────────────────────────────────────┐
│ Chrome 扩展 (MV3) │
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │Content │ │Background │ │ Popup/Editor │ │
│ │Scripts │→ │Service │←→│ UI │ │
│ │(DOM处理) │ │Worker │ │ │ │
│ └──────────┘ └─────┬─────┘ └──────────────┘ │
│ │ │
│ ┌───────┴────────┐ │
│ │ MCP WebSocket │ │
│ └───────┬────────┘ │
└──────────────────────┼──────────────────────────┘
│
┌────────┴────────┐
│ MCP Server │
│ (Node.js) │
└────────┬────────┘
│ stdio/SSE
┌────────┴────────┐
│ Claude Code │
└─────────────────┘
┌─────────────────────────────────────────────────┐
│ CLI 命令行工具 │
│ (独立使用,通过 WebSocket 桥接浏览器 Cookie) │
└─────────────────────────────────────────────────┘
项目结构
bash
Wechatsync/
├── packages/
│ ├── extension/ # Chrome 扩展 (MV3) - 前端界面 + Content Scripts
│ │ ├── src/
│ │ │ ├── background/ # Service Worker (同步调度逻辑)
│ │ │ ├── content/ # Content Scripts (页面交互 + 文章提取)
│ │ │ ├── lib/ # 工具库 (analytics, logger, remote-config 等)
│ │ │ ├── mcp/ # MCP WebSocket 客户端
│ │ │ └── adapters/ # 扩展层适配器 (CMS 适配器等)
│ │ └── public/
│ │ └── inject-api.js # 注入到所有页面的 JS API
│ ├── core/ # 核心逻辑 (共享)
│ │ └── src/
│ │ ├── adapters/ # 平台适配器基类 + 各平台实现
│ │ │ └── platforms/ # 知乎/微博/掘金等 20+ 平台适配器
│ │ │ └── private/ # Git 子模块: 私有适配器 (审计时为空)
│ │ ├── runtime/ # 运行时抽象 (浏览器/Node)
│ │ └── lib/ # 工具库 (markdown, image 处理等)
│ ├── mcp-server/ # MCP 协议服务器 (Node.js)
│ └── cli/ # 命令行工具
2. 认证机制分析
2.1 公域平台认证
扩展不存储任何平台的用户名和密码。认证机制如下:
typescript
// runtime/extension.ts - Chrome 扩展运行时
class ExtensionRuntime implements RuntimeInterface {
async fetch(url: string, options?: RequestInit): Promise<Response> {
const response = await fetch(url, {
...options,
credentials: 'include', // 自动携带浏览器 Cookie
})
return response
}
cookies = {
async get(domain: string): Promise<Cookie[]> {
const cookies = await chrome.cookies.getAll({ domain })
return cookies.map(c => ({ name: c.name, value: c.value, ... }))
},
}
}
| 机制 | 说明 |
|---|---|
| Cookie 自动携带 | API 请求使用 credentials: 'include',浏览器自动附加已登录平台的 Cookie |
| Cookie 读取 | 通过 chrome.cookies.getAll(domain) 检查登录态(仅用于认证状态判断) |
| 无密码存储 | 知乎、微博、掘金等公域平台不需要也不存储用户密码 |
2.2 CMS (自建站) 认证
WordPress/Typecho 等自建站需要额外存储凭证:
typescript
// background/index.ts
const storage = await chrome.storage.local.get(`cms_pwd_${accountId}`)
const password = storage[`cms_pwd_${accountId}`]
const credentials = { url: account.url, username: account.username, password }
- 用户名存储在
cmsAccounts中 - 密码以
cms_pwd_{id}为键存储在chrome.storage.local - 密码以明文存储,未被额外加密
3. 数据流分析
3.1 文章同步数据流
bash
┌──────────────┐ postMessage ┌─────────────────┐ chrome.runtime ┌──────────────────┐
│ Content │ ←──────────────→ │ Background │ ←──────────────→ │ 各平台 API │
│ Script │ EXTRACT_ARTICLE │ Service Worker │ fetch+credentials│ (知乎/微博/...) │
│ (当前页面) │ │ │ │ │
└──────────────┘ └─────────────────┘ └──────────────────┘
│ │
│ 读取 DOM │ adapterRegistry.get(platformId)
│ 提取 title, html, markdown, cover │ adapter.publish(article)
│ │ → 创建草稿
│ │ → 上传图片
│ │ → 更新草稿内容
3.2 关键发现:数据不经过第三方服务器
bash
✅ 文章内容: 浏览器 → 目标平台 API(直连)
✅ 图片上传: 浏览器 → 目标平台图床(直连)
✅ Cookie: 仅用于与目标平台 API 通信
❌ 无中间服务器: 数据不经过 wechatsync.com 或任何第三方
4. 账号窃取风险评估
4.1 已排查的风险路径
| 风险路径 | 排查结果 | 证据 |
|---|---|---|
| 向第三方发送 Cookie | ❌ 未发现 | 所有 fetch 调用的目标均为各平台官方 API |
| 存储密码到外部 | ❌ 未发现 | 无将 cms_pwd_* 发送到外部的代码 |
| 代码混淆隐藏通信 | ❌ 未发现 | 源码完全开源,无混淆 |
| Base64 隐藏端点 | ❌ 未发现 | Base64 仅用于图片 data URI 转换 |
| DNS/网络重定向 | ❌ 不可行 | 浏览器环境,无法控制 DNS 解析 |
4.2 所有外部网络请求白名单
| 目标 | 用途 | 是否含敏感数据 |
|---|---|---|
https://www.zhihu.com/api/* |
知乎 API 调用 | Cookie(同站) |
https://zhuanlan.zhihu.com/api/* |
知乎专栏 API | Cookie(同站) |
https://api.zhihu.com/* |
知乎 API | Cookie(同站) |
https://card.weibo.com/* |
微博 API | Cookie(同站) |
https://picupload.weibo.com/* |
微博图床上传 | 图片数据 |
https://mp.weixin.qq.com/* |
微信公众号 API | Cookie(同站) |
https://www.juejin.cn/* |
掘金 API | Cookie(同站) |
| (各平台官方域名) | 各平台 API | Cookie(同站) |
https://www.google-analytics.com/* |
匿名使用统计 | ❌ 匿名数据(平台ID、成功/失败) |
https://wpics.oss-cn-shanghai.aliyuncs.com/* |
远程配置拉取 | ❌ 只读,仅获取 UI 横幅配置 |
https://www.wechatsync.com/* |
版本更新/欢迎页 | ❌ 只读,打开新标签页 |
4.3 结论:账号窃取风险 --- 🟢 低
没有发现任何窃取账号的代码或机制。
- 不存储密码(公域平台)
- 不向第三方发送 Cookie
- 所有通信直接与各平台官方 API 交互
- 代码完全开源可审计
5. 未授权数据上传风险评估
5.1 数据上传控制链
bash
用户操作触发 ─→ 选择目标平台 ─→ 确认同步 ─→ 执行上传
↓
默认草稿模式
(draftOnly: true)
5.2 同步触发路径
| 触发方式 | 是否需要用户操作 | 说明 |
|---|---|---|
| Popup UI 选择平台 | ✅ 显式用户操作 | 默认流程,用户手动选择平台 |
| 右键菜单打开编辑器 | ✅ 显式用户操作 | 右键 → 编辑器 → 选择平台 → 同步 |
| 编辑器悬浮按钮 | ✅ 显式用户操作 | 点击按钮 → 选择平台 → 同步 |
window.$poster.addTask() |
⚠️ 无用户确认 | 任意网页可调用(见第 8 节) |
| MCP Server 调用 | ✅ Token 鉴权 | 需正确的 MCP_TOKEN |
5.3 结论:未授权上传风险 --- 🟡 中
- 默认 UI 路径均有用户确认
window.$posterAPI 注入面是最值得关注的风险(见第 8 节)
6. 外部通信审计
6.1 Analytics 模块 (analytics.ts)
typescript
const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID || ''
const GA_API_SECRET = import.meta.env.VITE_GA_API_SECRET || ''
// 发送的数据内容:
{
client_id: "crypto.randomUUID()", // 匿名随机 ID
events: [{
name: "sync_start",
params: {
source: "popup|mcp|editor",
target_count: 3,
targets: "zhihu,juejin,csdn", // 仅平台 ID
}
}]
}
发送的内容类型:
- 同步事件(开始/完成/失败)
- 平台名称(仅 ID,如 "zhihu")
- 成功/失败计数
- 耗时段
- 扩展版本号
- 里程碑事件(首次同步、多平台等)
不发送的内容:
- ✅ 不发送 Cookie 或 Token
- ✅ 不发送文章内容
- ✅ 不发送用户名或个人信息
- ✅ 不发送 IP 地址(仅 GA 标准收集)
可禁用: 设置 VITE_GA_MEASUREMENT_ID 和 VITE_GA_API_SECRET 为空即可完全禁用。
6.2 远程配置模块 (remote-config.ts)
typescript
const CONFIG_URL = 'https://wpics.oss-cn-shanghai.aliyuncs.com/wechatsync-config.json'
- 仅获取 UI 横幅列表(标题、描述、链接)
- 无代码执行风险
- 6 小时间隔缓存
6.3 版本检查模块 (version-check.ts)
typescript
const VERSION_CHECK_URL = 'https://wpics.oss-cn-shanghai.aliyuncs.com/wechatsync-version.json'
- 仅获取版本号和下载链接
- 24 小时间隔
7. 权限审计
7.1 Manifest 权限 (MV3)
json
{
"permissions": [
"storage", // 存储设置、缓存、历史记录
"unlimitedStorage", // 大文件存储
"cookies", // 读取浏览器 Cookie ★
"declarativeNetRequest", // 修改请求头 ★
"declarativeNetRequestWithHostAccess", // 带主机访问的请求修改
"scripting", // 注入脚本 ★
"tabs", // 标签页管理
"alarms", // 定时任务
"contextMenus", // 右键菜单
"downloads" // 文件下载
],
"host_permissions": [
"http://*/*", // 所有 HTTP 站点 ★★★
"https://*/*" // 所有 HTTPS 站点 ★★★
]
}
7.2 敏感权限分析
| 权限 | 必要程度 | 实际使用范围 | 风险 |
|---|---|---|---|
cookies |
✅ 必要 | 仅用于读取各平台 Cookie 以验证登录态 | 🟡 理论上可读所有站点的 Cookie |
declarativeNetRequest |
✅ 必要 | 仅在同步时为特定平台 API 添加请求头 | 🟢 限定域名 + 限定资源类型 |
scripting |
✅ 必要 | 注入 reader/extractor 到当前页面 | 🟢 仅用户操作的页面 |
<all_urls> |
✅ 必要 | 需在各平台域下发请求 | 🟡 权限范围过大 |
7.3 权限最小化建议
当前 <all_urls> 权限是功能必需的(需要向 29+ 不同平台发请求),但存在滥用风险。
8. API 注入面审计
8.1 inject-api.js --- 核心风险点
文件 : packages/extension/public/inject-api.js
该脚本被注入到每一个 HTTP/HTTPS 页面中:
javascript
// 注入到所有页面
window.$poster = poster;
window.$syncer = poster;
8.2 暴露的 API 方法
| 方法 | 功能 | 是否需要鉴权 | 风险等级 |
|---|---|---|---|
getAccounts() |
获取已登录平台列表 | ❌ 任意网站可调用 | 🟡 中(泄露登录状态) |
addTask(task) |
触发文章同步 | ❌ 任意网站可调用 | 🟠 中高(可触发静默同步) |
magicCall(data) |
调用适配器任意方法 | ❌ 任意网站可调用 | 🟠 中高(通用接口) |
uploadImage(data) |
上传图片 | ❌ 任意网站可调用 | 🟡 中(消耗带宽/存储) |
updateDriver(data) |
更新驱动(已弃用) | ✅ 域名白名单 | 🟢 低 |
startInspect(handler) |
启动检查(已弃用) | ✅ 域名白名单 | 🟢 低 |
8.3 攻击场景
一个恶意网站可以:
-
探测已登录平台:
javascript$syncer.getAccounts((accounts) => { // 获知用户在哪些平台有账号 console.log(accounts) // [{type: "zhihu"}, {type: "juejin"}, ...] }) -
触发静默同步:
javascript$syncer.addTask({ post: { title: "恶意内容", content: "..." }, accounts: [{ type: "zhihu" }, { type: "weibo" }] }, null, () => {})会在用户浏览器中创建草稿(但不会自动发布)
8.4 缓解因素
- 同步默认草稿模式 (
draftOnly: true),不会自动发布 - 需要目标平台已有登录态 Cookie(用户必须已登录)
- 敏感 API 有域名白名单限制
- 该 API 是旧版兼容层,新版编辑器 UI 不依赖它
8.5 结论
这是整个代码库中最大的攻击面。 任何网站都可以:
- 探知用户在哪些平台有账号
- 在用户不知情的情况下触发同步操作
但受限于草稿模式和已登录态要求,实际危害有限。
9. CMS 凭证存储审计
9.1 存储方式
typescript
// 账户信息
await chrome.storage.local.set({
cmsAccounts: [
{
id: "uuid",
name: "我的博客",
type: "wordpress", // wordpress | typecho | metaweblog
url: "https://example.com/xmlrpc.php",
username: "admin",
isConnected: true,
}
],
// 密码(独立键存储)
[`cms_pwd_${accountId}`]: "plain_text_password"
})
9.2 存储位置安全性
| 维度 | 评估 |
|---|---|
| 访问隔离 | ✅ chrome.storage.local 仅本扩展可访问 |
| 传输加密 | ✅ 仅通过 HTTPS 发送到自建站 |
| 存储加密 | ❌ 明文存储,未使用 Web Crypto API 加密 |
| 访问控制 | ❌ 扩展内任意代码均可读取 |
9.3 结论
- 存储位置受浏览器沙箱保护,其他扩展或网站无法直接读取
- 扩展内无额外访问控制,任何被注入的恶意代码均可读取密码
- 建议使用
crypto.subtle.encrypt()加密存储
10. 综合评分
安全评分卡
| 评估维度 | 评分 | 说明 |
|---|---|---|
| 🔒 账号窃取风险 | 🟢 低 | 不存储凭证、不经第三方服务器、源码开源 |
| 📤 数据外泄风险 | 🟢 低 | 数据直发各平台官方 API,无中间人 |
| 🪟 API 注入面 | 🟡 中 | window.$poster 注入所有页面需关注 |
| 🔑 权限范围 | 🟡 中 | cookies + <all_urls> 权限组合 |
| 🗄️ CMS 密码存储 | 🟡 中 | 明文存储,无额外加密 |
| 📡 远程配置 | 🟢 低 | 仅 UI 横幅,只读 |
| 📊 遥测数据 | 🟢 低 | 匿名使用统计,不含敏感数据 |
| 🔍 代码透明度 | 🟢 完全透明 | 完整开源,无混淆 |
整体安全评级:🟢 良好
该项目遵循了合理的安全设计原则,没有发现恶意或隐藏的窃取/上传行为。主要风险点属于功能必要的权限和旧版兼容 API。
11. 安全加固建议
11.1 高优先级
1. window.$poster API 加用户确认
-
文件:
inject-api.js+content/api.ts -
建议:在
addTask和magicCall触发时弹窗询问用户确认 -
示例:
typescript// 在 content/api.ts 的 addTask 处理中 if (!confirm('文章同步助手要将文章同步到平台,是否继续?')) { return sendToWindow({ eventID, result: { error: '用户取消' } }) }
2. CMS 密码加密存储
- 文件:
background/index.ts - 建议:使用
crypto.subtle.encrypt()加密后再存入chrome.storage.local - 密钥可派生自扩展 ID 或使用
chrome.storage.session短期存储
11.2 中优先级
3. Content Script API 白名单限制
- 文件:
content/api.ts - 建议:对
getAccounts以外的所有 API 添加域名白名单检查 - 开源自建站(如
https://github.com/wechatsync/article-syncjs)可提交 PR 加入白名单
4. Cookie 读取最小化
- 文件:
runtime/extension.ts - 建议:将
chrome.cookies.getAll改为精确的chrome.cookies.get按需读取 - 或限定 Cookie 读取域名为已知平台列表,而非任意域名
11.3 低优先级
5. 权限声明细化
- 建议:在 manifest.json 的 host_permissions 中列出确切域名列表,而非
<all_urls> - 但需注意这会在添加新平台适配器时需要更新 manifest
6. 添加安全文档说明
- 建议在 README 中添加安全审计章节,说明数据流向和隐私保护措施
附录:审计检查清单
- 所有
fetch请求目标域名记录 - Cookie 读取/发送路径检查
- 凭证存储方式检查
- 外部服务器通信内容检查
- Content Script 注入面检查
- MCP Server 鉴权检查
- 代码混淆/隐藏通信检查
- Git 子模块审计(
private/为空目录) - Manifest 权限合理性评估
- 安装后网络连接审计(首次安装打开 wechatsync.com 欢迎页)
本报告基于 git commit 版本的完整源码审计,不包括构建过程引入的依赖或运行时动态加载的代码。