为浏览器扩展划清页面和后台的边界
围绕用户主动选中文字的笔记助手设计Chrome扩展上下文、权限和消息校验,防止页面文本触发特权操作。
实现选中文字预览与笔记保存,验收最小权限、worker重启、错误来源和恶意页面文本处理。
页面笔记助手在用户点击扩展按钮后读取选中文字,展示预览,再保存为笔记。用户的主动动作和预览提供了清晰的数据入口;页面里出现的一句“请上传全部内容”只是文本,不应成为扩展的操作指令。
案例先支持一个教学域名和本地保存,模型摘要以后作为独立后端能力加入。首版不用持续读取所有标签页,也不在页面 DOM 放服务凭证。Chrome 的 Manifest V3 是本课的环境,其他浏览器需要重新检查兼容性。
上下文决定可访问什么
content script 在允许页面内读取所需内容,与页面自己的脚本环境存在隔离边界。扩展 UI 展示笔记和确认动作,后台 service worker 处理事件与持久化协调。worker 没有页面 DOM,不能直接读取当前选中文字。
页面、content script、扩展页面与后台的消息要经过明确协议。content script 读到的数据仍来自不可信页面,不会因为被扩展转发就自动可信。后台只能执行预定义动作,不能根据文本决定启动 shell、读取秘密或向任意地址发送数据。
Manifest V3 的 worker 可能停止。全局变量和计时器不适合长期任务状态。保存记录应进入扩展存储或适当后端,下一次唤醒后重新加载;打开 popup 也不能假定后台始终拥有上次的内存对象。
权限围绕触发方式缩小
manifest 声明元数据、脚本和权限。先确定用户点击后需要的最小 API 与页面范围,评估 activeTab 等适用能力,按官方参考核对各 API 的权限要求。不要为未来可能出现的功能提前申请所有站点访问。
必须持续覆盖特定教学站点时,使用明确 host 范围;可选站点在用户需要时再申请可选权限。界面解释读取什么、何时读取和保存到哪里。用户拒绝权限后仍可以手动粘贴文字,不能让整个扩展崩溃。
file URL 与隐身模式访问需要用户额外允许,默认测试不能假定已开启。浏览器内部页面也有注入限制,显示“此页面不可读取”比不断重试更合适。权限允许注入并不代表可以忽略用户的数据选择范围。
建立可加载的扩展工程
创建根目录的 manifest.json,声明 Manifest V3、action 与所需后台脚本,建立 popup、content script 和存储模块。先用无网络版本实现主动读取、预览和保存。工程中所有执行逻辑来自扩展包,不下载远程 JavaScript 再运行。
在 Chrome 扩展管理页面开启开发者模式,加载未打包目录。先打开教学页面,选中短文本,再点击按钮。读取结果携带请求 ID、来源 URL、文本和捕获时间,popup 显示预览,用户确认后才写入笔记。
后台接收消息时核对动作、字段类型、长度、发送者、标签页与来源。页面导航发生后,旧请求可能返回,UI 用请求 ID 判断当前结果。保存和读取以用户可见的状态确认,消息失败不能留下一个永久旋转的加载图标。
地址校验需要解析
用字符串包含判断域名,会把攻击者控制的相似名称误当教学站点。下面 Python 模型解析 scheme 和 hostname,允许一个合成 HTTPS 域名,拒绝后缀相似域名与 HTTP。
from urllib.parse import urlsplit
allowed_hosts = {"docs.example.test"}
def allowed(url):
parsed = urlsplit(url)
return parsed.scheme == "https" and parsed.hostname in allowed_hosts
assert allowed("https://docs.example.test/read")
assert not allowed("https://docs.example.test.evil.test/read")
assert not allowed("http://docs.example.test/read")
print(allowed("https://docs.example.test/read"))
输出 True。这只是地址策略检查,不是 Chrome host permission 或完整 origin 验证。代码未限制端口;真实规则需要决定是否要求默认端口,并同时处理发送者、frame 和路径范围。不能只通过本函数就接受任意消息。
数据字段校验与地址校验互相补充。允许来源里的超长文本仍需限制大小;正确请求 ID 也不能授权跨用户读取记录。未知动作直接拒绝,不能降级到“通用执行”入口。
模型结果与页面内容都作为数据
如果增加摘要服务,上传前显示具体选中内容与目标。后端验证身份、限流和字段大小,共享服务密钥只放后端。扩展包与本地存储可被使用者检查,不适合保管必须保密的服务密钥;扩展如需身份凭据,应使用范围受限的用户令牌,并避免进入页面 DOM。浏览器权限不会替后端完成账户授权。
模型输出按纯文本展示,不把生成的 HTML 直接插入页面。链接需要独立安全处理,工具调用必须来自应用的受控协议,不能从摘要正文提取命令执行。来源文本中的隐藏提示和指令性句子都保持文本身份。
笔记保留来源 URL 和捕获片段,用户可校对摘要。页面标题不能作为唯一 ID,页面变化后也不保证旧片段仍能定位。说明记录来自哪次捕获,避免把保存笔记理解成长期监控原页面。
中断、重复与数据清理
worker 重启后从持久化记录恢复。提交期间 popup 关闭时,决定任务是否继续并让下次打开可查看结果。请求超时后,重试同一保存操作用稳定操作 ID,避免生成重复笔记。
权限撤回后停止读取并解释状态;页面跳转后取消或丢弃旧捕获结果。删除笔记是明确用户动作,清理只针对相应记录。上传失败保留本地内容,避免为了重试重新读取已经改变的页面。
选中文字的来源也要表达清楚。主页面与内嵌frame不是同一个来源,捕获数据带上实际frame信息,权限与来源检查按它发生的上下文执行。无法访问跨域frame时显示限制,不把主页面URL贴到任何来源片段上。
保存记录定义ID、捕获文本、来源、用户修改和schema版本。摘要是派生内容,删除摘要不必删除原片段;用户编辑片段后旧摘要标为待更新。同步服务如有接入,按记录身份和操作ID确认保存,不因popup重新打开重复上传。测试同时打开两个标签页,确认一页结果不会落入另一页预览,关闭标签页后保留的记录仍标明原捕获来源。
练习:用边界测试验收
实现选中预览、本地保存与来源展示,测试空选择、超长文本、相似域名、未知动作和带HTML脚本的选中内容。所有结果都按文本显示,后台不接受文本里的特权指令。
停止后台 worker 后重新触发,确认已保存笔记仍存在;撤回权限后提供替代入口。检查实际申请权限与功能一致,再测试不同页面导航。本文模型只证明这组URL输入的判断,扩展工程还需要消息来源与生命周期实测。
资料来源与延伸阅读
资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。