Skip to content

安全与权限

LD-Notion 同时支持用户手动操作和 AI 触发操作。为了避免自然语言误操作直接写入工作区,写入口会经过统一守卫层。

权限等级

等级能力边界适合场景
只读搜索、读取、查看详情初次体验、只问不改
标准创建页面、写内容、更新属性、自动分类日常整理
高级移动、复制、归档、数据库结构类操作深度整理
管理员高风险管理操作确认后短期开启

OperationGuard

mermaid
flowchart TD
  Action[用户或 AI 请求操作] --> Classify[识别操作类型]
  Classify --> Read{只读?}
  Read -->|是| AllowRead[直接执行]
  Read -->|否| Permission{权限足够?}
  Permission -->|否| Deny[拒绝并提示]
  Permission -->|是| Dangerous{危险操作?}
  Dangerous -->|否| Audit[记录审计]
  Dangerous -->|是| Confirm[请求用户确认]
  Confirm -->|取消| Deny
  Confirm -->|确认| Undo[设置撤销窗口]
  Undo --> Audit
  Audit --> Execute[执行写入]

审计日志

审计日志用于回答两个问题:

  1. AI 或用户刚刚做了什么。
  2. 如果结果不符合预期,应该从哪一步排查。

日志可在面板中查看和清除。

OAuth 凭证存储模型(v3.12.0 调整)

  • 敏感凭证分两类存储:
    • OAuth 三键Client Secret / access token / refresh token / manual Integration Token)自 v3.12.0 起保存在浏览器本地 GM 明文存储。原因是 OAuth 授权回调与 refresh 续签天然发生在全新页面,而加密保险箱每次页面加载即重新锁定,锁定态下读空会导致授权必败;明文存储保证跨页可读。
    • 其它敏感凭证(AI API Key / Base URL、GitHub Token、Obsidian API Key / URL)仍默认写入本地加密保险箱(AES-256-GCM,PBKDF2 200K 迭代),需用户本地设置口令并在当前会话解锁后才可读。
  • 审计安全:OperationLog.redactSensitiveFields 使用 REDACT_IN_LOGS 超集(加密保险箱键 + OAuth 三键,共 8 键),所有敏感凭证在审计日志中一律 ***REDACTED*** 脱敏,明文存储不会泄漏到日志。
  • 非敏感配置仍保存在浏览器本地存储中,例如目标数据库 ID、面板位置、来源偏好和 OAuth 的 Client ID / Redirect URI
  • 「断开授权」只清除本地 access token / refresh token,不会撤销 Notion 后台已经批准的授权,也不会自动删除你保留的 OAuth 基础配置。
  • 升级提示:v3.12.0 之前已迁入加密保险箱的 OAuth 凭据无法自动回读,升级后如遇 OAuth 字段为空,重新输入一次 Client Secret 并重新授权即可。

v3.7.0 安全加固

v3.7.0 对用户脚本权限域和 AI 输入链路做了系统性加固:

Userscript 权限域收窄

  • @match*://*/* 替换为 6 个显式站点模式(linux.do、notion.so、github.com、zhihu.com)。
  • @connect* 替换为 9 个显式域名白名单(api.notion.com、linux.do、s3.amazonaws.com、api.openai.com、api.anthropic.com、generativelanguage.googleapis.com、api.github.com、zhihu.com)。
  • 新增 @include 正则白名单 + @exclude 排除搜索引擎/邮箱/localhost,提供纵深防御。
  • 这阻止了用户脚本向任意域名发起网络请求(如攻击者控制的 exfil 端点)。

AI Prompt Injection 多层防御

  • XML 标签隔离:AI 分类 prompt 将用户内容包裹在 <user_content> XML 标签中,与系统指令分离,降低 prompt injection 风险。
  • ChatUI 输出净化escapeHtml 使用浏览器原生 textContent→innerHTML 转义;safeMarkdown 先转义全部 HTML,再选择性恢复安全 Markdown 格式(粗体、换行),防止注入 HTML/JS 在聊天 UI 中渲染。
  • UI 全局 escapeHtml:所有用户可控文本(数据库名、页面标题、错误消息、书签标题、标签、文件名等)在插入 HTML 前统一经过 Utils.escapeHtml 转义,覆盖 50+ 处拼接点。

OperationGuard setLevel 验证

setLevel 方法现在强制校验输入值必须为 0-3 的整数,拒绝 NaNInfinity、负数或超范围值,防止无效权限级别绕过权限系统。

已知待办

  • Extension SSRF 白名单严格匹配:当前 background service worker 的 URL 白名单使用简单字符串匹配,可被 evil.amazonaws.com.attacker.com 绕过。应改用 URL 构造函数解析 hostname 后精确匹配,并限制协议为 https、端口为默认端口。(已在 v3.7.4 修复)
  • Extension CredentialVault 移植:Chrome Extension 版本中 API key 通过 chrome.storage.local 明文存储,CredentialVault AES-256-GCM 加密机制尚未移植到 Extension 侧。

v3.7.8 安全加固

v3.7.8(ISS-20260723-009,CWE-94/918)针对 AI 输出经 prompt injection 写入恶意 URL 或异常属性的问题,新增统一的 AI 输出 schema 校验层,并顺带修复了导入页面 HTML 的 SSRF sibling。

AI 输出 schema 校验层

新增 src/ai/schema.jsAISchema),统一校验 AI 返回并直接写入 Notion 的内容:

  • validatePageExternalUrl:转发 UrlValidator.validatePageExternalUrl,校验 AI 返回的 icon/cover external.url。
  • validatePropertyName:属性名白名单(中英数字 + 下划线/连字符/空格)+ 截断 ≤64 + 拒 Notion 保留名(title/created_time/last_edited_time/created_by/last_edited_by/url/path/Name)。
  • validatePropertyType:类型白名单,拒 relation/people/files 等系统关联字段。
  • validatePropertyValue:title/rich_text ≤2000、select ≤100、number isFinite+|v|<1e15、date ISO8601、checkbox Boolean。
  • validateEmoji:≤32 + 拒控制字符。
  • sanitizeObjectValue:对象值白名单,拒 relation/people/created_by/created_time/last_edited_time 等系统字段,防 schema 污染。
  • validateExtractToDatabaseSchemaproperties/entries 需为数组,非数组返回明确 reason,不再让 TypeError 被吞。
  • parseAIJson:统一入口(正则提取 + JSON.parse + 按 name 路由校验),消除 7 消费点重复的 jsonMatch+JSON.parse+try-catch 三段式,并为 ai/index.js 拆分(ISS-010)预留接缝。

src/ai/index.js 7 个消费点接入:icon/cover URL、属性名/类型/值、对象值白名单、handleExtractToDatabase(含失败条目计数回显)、parseIntent(intent 白名单 + compound steps 上限 20)、handleEditContentcontent_updatesold_str/new_str 结构校验)。

UrlValidator 补充原语

src/security/UrlValidator.js 新增 validatePageExternalUrl:限定 http(s) 协议(拒 javascript:/data:/file:)+ _isPrivateHost 拒内网/私有/链路本地(10.x/172.16-31.x/192.168.x/169.254.x/127/localhost)。防 Notion 服务端抓取 external.url 触发云元数据 SSRF(169.254.169.254)。

DOMToNotion SSRF sibling(CWE-918)

src/api/index.jsDOMToNotion 有 7 处 external.url 消费点(_cookLightbox/_cookAttachment/_cookVideo/_cookAudio/_cookImage/_cookParagraph 内 img + attachment),把 Utils.absoluteUrl(帖子 HTML 的 src/href) 直写 Notion external.url。帖子作者可写 <img src="http://169.254.169.254/...">,导入时 Notion 服务端抓取触发 SSRF。来源与 AI 输出不同(帖子 HTML vs AI 输出)但同漏洞模式同触发点。新增 _safeExternalUrl helper(复用 validatePageExternalUrl)全部接入,合法外网 CDN 图片/附件不受影响(_isPrivateHost 只拒内网)。

v3.7.7 安全加固

v3.7.7(odyssey-improve 6 维度审计)修复了 UI XSS 与自动同步归档绕过权限闸门两类问题。

XSS 渲染未转义(CWE-79)

4 处 innerHTML 拼接补 escapeHtml

  • main-ui.js / notion-site-ui.js 的 AI 模型 <option> 的 value 与文本。
  • events.js 模板中的 icon。
  • security/index.js 确认对话框 hint 中的 itemName(同块 placeholder 已转义,hint 行遗漏)。

自动同步归档绕过 OperationGuard(CWE-862/639)

BookmarkAutoImporter 自动同步归档已删除书签时直连 NotionAPI.deletePage 绕过 OperationGuard(deletePage level 2)。改为 OperationGuard.canExecute 权限闸门,权限不足时跳过归档并记 guard.denied 审计,不裸调。详见 OperationGuard

v3.7.4 安全加固

v3.7.4 在 v3.7.3 基础上进一步收紧 URL 安全与输入处理:

URL 安全与 SSRF

  • Extension background worker 协议限制isUrlAllowed 现在解析 URL 后,对非本地地址强制校验 parsed.protocol === "https:" 且端口为默认 443,防止通过 http://allowed-host.evil.com 或自定义端口绕过白名单。
  • 循环依赖消除UrlValidator 提取到 src/security/UrlValidator.js 独立模块,消除 src/api/index.jssrc/security/index.js 之间的 circular dependency,降低模块加载阶段的安全工具不可用风险。

输入净化

  • post.cooked 不再直接 innerHTMLsrc/export/index.js 的帖子文本提取改用 DOMParser.parseFromString(..., "text/html") 后读取 textContent,避免不可信 HTML 在解析阶段执行脚本。

弱随机数

  • Math.random() 剩余两处清除src/api/index.js 的 multipart boundary 和 src/ui/events.js 的 Obsidian 图片文件名统一改用 crypto.getRandomValues 生成随机 hex 字符串。

v3.7.3 安全加固

v3.7.3 针对 API key 泄露和弱随机数做了专项修复:

API Key 泄露防护

  • AI 请求 baseUrl 校验AIService._normalizeBaseUrl 内置 UrlValidator.validateAiBaseUrl,仅允许白名单域名(api.openai.comapi.anthropic.comgenerativelanguage.googleapis.com)或 HTTPS 非内网域名,阻止攻击者通过自定义 baseUrlAuthorization 头重定向到恶意服务器(SEC-001)。
  • Obsidian API URL 本地限制ObsidianAPI 三个方法入口调用 UrlValidator.validateObsidianUrl,仅允许 127.0.0.1/localhost/::1,阻止篡改存储值后的 API key 泄露(SEC-002)。
  • 私有网段拦截UrlValidator._isPrivateHost 拦截 10.x/172.16-31.x/192.168.x/169.254.x 私有 IP 和 link-local 地址,防止 SSRF。

弱随机数消除

  • OAuth state tokenUtils.randomToken 移除 Math.random() 回退,在 crypto.getRandomValues 不可用时抛出错误(SEC-005)。
  • 审计日志 event IDOperationLog.createEventIdMath.random() 改为 crypto.getRandomValues(SEC-015)。
  • API key hashWorkspaceService.buildWorkspaceDataapiKeyHash 从直接截取后 8 位改为 djb2 hash,避免部分暴露 API key(SEC-011)。

v3.7.2 UI 安全加固

v3.7.2 通过 UI Odyssey 全维度审查修复了 UI 层面的安全问题:

innerHTML 注入防护

  • NotionSiteUI.showStatusUI.showStatus 两处状态显示函数通过 innerHTML 渲染消息内容,此前直接插入 messagetype 参数,存在 XSS 风险。
  • 现在两处均使用 Utils.escapeHtml(message)Utils.escapeHtml(type) 对动态内容转义后再插入。
  • GenericUI.showStatus 使用 textContent 赋值,天然免疫 XSS,无需修改。
  • Obsidian 测试连接状态的 innerHTMLresult.errore.message 也已加 escapeHtml

导出操作防重入

  • exportBtn.onclickobsExportBtn.onclick 入口处检查 disabled 状态,操作中设 disabled = truefinally 中恢复 disabled = false,防止并发点击导致重复导出。

除零与 DOM 爆炸防护

  • showProgresscurrent / total 除法增加 total > 0 前置检查,避免 total = 0 时产生 NaN
  • 导出报告失败项截断为最多 20 条,错误文本截断为 120 字符,防止大量失败项导致 DOM 爆炸。

状态定时器冲突修复

  • showStatus 连续调用时,旧定时器可能在新消息显示期间触发 container.innerHTML = "",导致新消息被提前清除。
  • 现在每次调用前先 clearTimeout(container._statusTimer),确保只有最新的定时器生效。

推荐安全实践

  • 日常使用保持「标准」权限。
  • 危险操作确认保持开启。
  • 只在需要移动、归档、数据库结构操作时临时切到「高级」。
  • 首次保存 AI API Key、GitHub Token、Obsidian 等敏感凭证前,先初始化并解锁本地保险箱(OAuth 三键自 v3.12.0 起走 GM 存储,无需此步)。
  • 不要把共享生产级 OAuth Client Secret 放进前端配置。
  • 在批量操作前先对少量数据试运行。