安全与权限
LD-Notion 同时支持用户手动操作和 AI 触发操作。为了避免自然语言误操作直接写入工作区,写入口会经过统一守卫层。
权限等级
| 等级 | 能力边界 | 适合场景 |
|---|---|---|
| 只读 | 搜索、读取、查看详情 | 初次体验、只问不改 |
| 标准 | 创建页面、写内容、更新属性、自动分类 | 日常整理 |
| 高级 | 移动、复制、归档、数据库结构类操作 | 深度整理 |
| 管理员 | 高风险管理操作 | 确认后短期开启 |
OperationGuard
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[执行写入]审计日志
审计日志用于回答两个问题:
- AI 或用户刚刚做了什么。
- 如果结果不符合预期,应该从哪一步排查。
日志可在面板中查看和清除。
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 迭代),需用户本地设置口令并在当前会话解锁后才可读。
- OAuth 三键(
- 审计安全:
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 的整数,拒绝 NaN、Infinity、负数或超范围值,防止无效权限级别绕过权限系统。
已知待办
Extension SSRF 白名单严格匹配:当前 background service worker 的 URL 白名单使用简单字符串匹配,可被(已在 v3.7.4 修复)evil.amazonaws.com.attacker.com绕过。应改用 URL 构造函数解析 hostname 后精确匹配,并限制协议为 https、端口为默认端口。- 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.js(AISchema),统一校验 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、numberisFinite+|v|<1e15、date ISO8601、checkbox Boolean。validateEmoji:≤32 + 拒控制字符。sanitizeObjectValue:对象值白名单,拒relation/people/created_by/created_time/last_edited_time等系统字段,防 schema 污染。validateExtractToDatabaseSchema:properties/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)、handleEditContent(content_updates 的 old_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.js 的 DOMToNotion 有 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.js与src/security/index.js之间的 circular dependency,降低模块加载阶段的安全工具不可用风险。
输入净化
post.cooked不再直接innerHTML:src/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.com、api.anthropic.com、generativelanguage.googleapis.com)或 HTTPS 非内网域名,阻止攻击者通过自定义baseUrl将Authorization头重定向到恶意服务器(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 token:
Utils.randomToken移除Math.random()回退,在crypto.getRandomValues不可用时抛出错误(SEC-005)。 - 审计日志 event ID:
OperationLog.createEventId从Math.random()改为crypto.getRandomValues(SEC-015)。 - API key hash:
WorkspaceService.buildWorkspaceData的apiKeyHash从直接截取后 8 位改为 djb2 hash,避免部分暴露 API key(SEC-011)。
v3.7.2 UI 安全加固
v3.7.2 通过 UI Odyssey 全维度审查修复了 UI 层面的安全问题:
innerHTML 注入防护
NotionSiteUI.showStatus和UI.showStatus两处状态显示函数通过innerHTML渲染消息内容,此前直接插入message和type参数,存在 XSS 风险。- 现在两处均使用
Utils.escapeHtml(message)和Utils.escapeHtml(type)对动态内容转义后再插入。 GenericUI.showStatus使用textContent赋值,天然免疫 XSS,无需修改。- Obsidian 测试连接状态的
innerHTML中result.error和e.message也已加escapeHtml。
导出操作防重入
exportBtn.onclick和obsExportBtn.onclick入口处检查disabled状态,操作中设disabled = true,finally中恢复disabled = false,防止并发点击导致重复导出。
除零与 DOM 爆炸防护
showProgress中current / 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 放进前端配置。
- 在批量操作前先对少量数据试运行。