Skip to content

插件安全性审查指南

Obsidian 插件生态的开放性是其核心优势,但也带来了安全风险。本文为插件开发者提供安全审查清单和最佳实践,帮助确保插件安全可靠。

为什么插件安全很重要

Obsidian 插件拥有对本地文件系统的完全访问权限,这意味着:

  • 可以读取、修改、删除你的所有笔记
  • 可以访问网络(发送数据到外部服务器)
  • 可以执行任意代码
  • 可以访问剪贴板内容

安全提醒

Obsidian 官方对社区插件进行代码审查,但无法保证 100% 安全。用户应自行评估插件的可信度。

安全审查清单

1. 网络请求

检查项

  • [ ] 所有网络请求是否必要?
  • [ ] 是否向第三方服务器发送用户数据?
  • [ ] 网络请求是否使用 HTTPS?
  • [ ] 是否在设置中明确告知用户数据发送行为?
  • [ ] 请求频率是否合理?

最佳实践

typescript
// ❌ 不好的做法:静默发送用户数据
async function sendData(content: string) {
  await requestUrl({
    url: 'https://example.com/api',
    method: 'POST',
    body: JSON.stringify({ content })
  });
}

// ✅ 好的做法:告知用户并获得同意
async function sendData(content: string) {
  if (!this.settings.allowDataSending) {
    new Notice('请在设置中启用数据发送功能');
    return;
  }
  await requestUrl({
    url: 'https://example.com/api',
    method: 'POST',
    body: JSON.stringify({ content }),
    headers: { 'Content-Type': 'application/json' }
  });
}

2. 文件系统操作

检查项

  • [ ] 文件读取是否限制在必要范围?
  • [ ] 文件写入是否有备份机制?
  • [ ] 文件删除是否有确认提示?
  • [ ] 路径处理是否防止目录遍历攻击?
  • [ ] 是否正确处理文件名中的特殊字符?

最佳实践

typescript
// ❌ 不好的做法:直接使用用户输入作为路径
const path = `${vaultPath}/${userInput}`;

// ✅ 好的做法:验证和规范化路径
import { normalizePath } from 'obsidian';

function safePath(userInput: string): string {
  const normalized = normalizePath(userInput);
  // 防止目录遍历
  if (normalized.startsWith('..') || normalized.startsWith('/')) {
    throw new Error('Invalid path');
  }
  return normalized;
}

3. 代码执行

检查项

  • [ ] 是否使用了 eval() 或类似动态代码执行?
  • [ ] 是否使用了 child_process 执行系统命令?
  • [ ] 是否加载了外部脚本?
  • [ ] 动态代码执行是否可被用户输入注入?

最佳实践

typescript
// ❌ 不好的做法:使用 eval 执行动态代码
const result = eval(userExpression);

// ✅ 好的做法:使用安全的替代方案
// 如果需要数学表达式,使用专门的数学解析库
import { evaluate } from 'mathjs';
const result = evaluate(userExpression);

4. 数据存储

检查项

  • [ ] 敏感数据(API Key、密码)是否加密存储?
  • [ ] 插件数据是否保存在 .obsidian/plugins/ 目录中?
  • [ ] 是否避免在笔记中明文存储凭据?
  • [ ] 数据格式是否使用标准 JSON?

最佳实践

typescript
// ❌ 不好的做法:明文存储 API Key
this.settings.apiKey = 'sk-xxxxxxxx';

// ✅ 好的做法:使用 Obsidian 的安全存储
import { loadLocalStorage, saveLocalStorage } from 'obsidian';

// 存储敏感信息
saveLocalStorage('my-plugin-api-key', apiKey);

// 读取敏感信息
const apiKey = loadLocalStorage('my-plugin-api-key');

5. 依赖安全

检查项

  • [ ] 依赖包是否来自可信来源?
  • [ ] 依赖包数量是否最小化?
  • [ ] 是否定期更新依赖修复安全漏洞?
  • [ ] 是否使用 package-lock.json 锁定版本?

最佳实践

json
// package.json — 最小化依赖
{
  "dependencies": {
    // 只保留必要的依赖
    // 避免引入大型工具库(如 lodash)如果只用其中一两个函数
  }
}

6. 隐私保护

检查项

  • [ ] 是否收集用户使用数据?
  • [ ] 隐私政策是否清晰说明数据使用?
  • [ ] 用户是否可以选择退出数据收集?
  • [ ] 是否遵循 GDPR 等数据保护法规?

安全发布流程

发布前检查

  1. 运行安全审查清单
  2. 确保所有设置项有合理的默认值
  3. 编写安全相关的 README 说明
  4. 测试极端输入和边界情况

README 安全说明模板

markdown
## 安全说明

### 数据处理
- 本插件 [不/会] 发送数据到外部服务器
- 发送的数据包括:[列出数据类型]
- 数据发送目的地:[列出服务器]

### 权限需求
- 文件读取:[说明原因]
- 文件写入:[说明原因]
- 网络访问:[说明原因]

### 隐私
- 本插件 [不/会] 收集使用数据
- 敏感信息存储方式:[说明]

版本更新安全

  • 在 CHANGELOG 中记录安全修复
  • 对破坏性变更提供迁移指南
  • 不自动移除安全相关设置
  • 使用语义化版本号标记安全修复

常见安全问题

1. XSS 攻击

typescript
// ❌ 不好的做法:直接插入 HTML
containerEl.innerHTML = userInput;

// ✅ 好的做法:使用 DOM API 或转义
containerEl.createEl('span', { text: userInput });

// 或使用转义函数
function escapeHtml(str: string): string {
  return str.replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;',
    '"': '&quot;', "'": '&#039;'
  })[c]);
}

2. 正则表达式拒绝服务(ReDoS)

typescript
// ❌ 不好的做法:可能导致 ReDoS 的正则
const regex = /(a+)+b/;

// ✅ 好的做法:简化正则表达式
const regex = /a+b/;

3. 原型污染

typescript
// ❌ 不好的做法:深度合并可能导致原型污染
function deepMerge(target: any, source: any) {
  for (const key in source) {
    target[key] = typeof source[key] === 'object'
      ? deepMerge(target[key] || {}, source[key])
      : source[key];
  }
  return target;
}

// ✅ 好的做法:使用 Object.create(null) 和白名单
function safeMerge(target: any, source: any, allowedKeys: string[]) {
  for (const key of allowedKeys) {
    if (key in source) {
      target[key] = source[key];
    }
  }
  return target;
}

用户端安全建议

作为插件使用者,也应关注安全问题:

  1. 只安装必要的插件 — 每个插件都是潜在的安全风险
  2. 检查插件源码 — 特别是涉及网络请求和文件操作的插件
  3. 关注插件更新 — 及时更新以获取安全修复
  4. 定期审查已安装插件 — 移除不再使用的插件
  5. 备份重要数据 — 在安装新插件前备份仓库

相关资源

最后更新:2026年4月11日编辑此页反馈问题