| Version | Supported |
|---|---|
| 1.x | ✅ |
| < 1.0 | ❌ |
扩展由 .github/workflows/release.yml 从打标签的版本发布,只维护最新一条版本线。
The extension is published from tagged releases; only the latest version line is maintained.
它拿的是 <all_urls> 主机权限,能为任意页面代发请求并注入请求头,也能把页面 fetch / XMLHttpRequest / WebSocket 改道到另一个环境。也就是说,一个规则注入校验漏洞、一条让被阻断请求真的发出去的回归、或一处把跨源消息当成可信来源的判断失误,都可能变成目标环境 token 泄露或请求被悄悄改写。这类问题请不要开公开 Issue。
It holds the <all_urls> host permission, can issue requests on behalf of any page and inject headers, and can redirect page fetch / XHR / WebSocket traffic elsewhere. A flaw in header validation, in block-rule enforcement, or in message-origin checks could leak a target-environment token or silently rewrite requests. Please don't open a public issue for that.
- 首选:仓库首页 Security → Report a vulnerability(私密漏洞报告),填写复现步骤即可,只有你与维护者能看到。该入口需要仓库所有者先在
Settings → General → Advanced勾选 Privately report a security vulnerability(见 GITHUB.md §6)。 Preferred: use Security → Report a vulnerability on the repository home page. It is private to you and the maintainers. The owner must first enable Privately report a security vulnerability underSettings → General → Advanced. - 入口未开启时,开一个标题只写
security的最小 Issue 并@维护者,不要在正文里贴漏洞细节,约到私密渠道再展开。 If that entry point is off, open a minimal issue titled onlysecuritywithout any details and ask for a private channel. - 复现材料里请用
fat-api.example.com/uat-api.example.com这类示例域名,不要贴真实内网域名、token 或抓包数据——Issue 正文是公开的,仓库与商店审核都会看到。 Use*.example.comdomains in reports. Never paste real internal hostnames, tokens or captured traffic: issue text is public.
- 收到后先确认并给出是否复现的结论与临时规避手段(单人维护,不承诺固定 SLA)。 We acknowledge first and come back with reproduction status plus a workaround; it is a solo-maintained project, so no fixed SLA is promised.
- 确认的问题优先出补丁版本并按 RELEASING.md 走商店提审;商店审核时长不受本仓库控制,会在补丁发布时一并说明。 Confirmed issues ship as a patch release through the store; store review latency is outside our control and gets called out in the release notes.
- 修复会写进 CHANGELOG.md;是否署名致谢完全尊重报告者意愿,不公开未修复的细节。
Fixes are listed in
CHANGELOG.md; attribution follows the reporter's preference, and unpatched details stay undisclosed.
内容脚本注入每个页面,因此任意 http(s) 页面上的脚本都能借桥接层发出 PROXY_REQUEST——扩展对页面而言是一个潜在的 confused deputy。约束不在来源门禁(内容脚本的 sender.url 就是页面 URL,加上去代理当场失效),而在「只按规则代发」:页面不能指定目标地址,请求只会落到用户已配置、且此刻仍匹配该 URL 与方法的规则上;页面传来的 ruleId 只是候选,由后台复核(存在、启用、仍匹配),能注入的头与能改写的响应都来自规则本身。这条边界有一个已知代价:如果一条规则的匹配面足够宽(例如 https://*/*)且注入了凭据头或打开了「携带 Cookie」,那么任意页面都能借它读到目标环境的响应。这正是规则应当写得足够窄、凭据位点优先用 {{名称}} 引用的理由;扩展刻意不附加「发起页必须与规则相关」这层校验,因为规则本就以请求地址而不是发起页为作用域。
Content scripts run in every page, so any script on an http(s) page can emit a PROXY_REQUEST through the bridge — the extension is a potential confused deputy for the page. The constraint is not an origin gate (a content script's sender.url is the page's own URL, so gating it would disable proxying) but the rule-only contract: the page cannot name a destination, and a request is forwarded only under a rule the user already configured that still matches that URL and method. The page-supplied ruleId is a candidate the background re-validates (exists, enabled, still matches), and every injected header or rewritten response comes from the rule itself. The known cost of this boundary: if a rule's match surface is broad enough (say https://*/*) and it injects a credential header or turns on Send cookies, any page can use it to read responses from the target. That is why rules should be written narrow, and why credential slots belong in {{NAME}} references; the extension deliberately adds no "the initiating page must relate to the rule" check, because rules are scoped by request URL, not by initiating page.
请求头名校验且拒绝 CRLF、状态码钳制在 200–599、请求体 10 MB 上限(按 UTF-8 字节量最终出站 body,规则覆盖的 body 同样计入)、被规则拒绝的请求在本地日志留下原因、用户正则的嵌套量词(ReDoS)筛查、送给网络层的正则必须过 Chrome 的 RE2 校验且替换引用越界检查、跨 world 消息一律以 window.location.origin 为目标源、跨 world 的代理响应在桥接层整形(失败信封补齐 requestId 与数值 status,后台内部错误文案不外泄给页面)、响应体改写的路径写入拒绝 __proto__ / constructor / prototype 键名、修改状态的消息需过 isTrustedSender、导入的 HAR/cURL/JSON 在边界处过滤非法项(导入与抓包路径上的头映射还会逐条丢弃非法项)、导出默认剔除凭据(Authorization / Cookie 等头,配置导出另含 token 类查询参数覆盖,取消勾选才做原样本地备份)、搜索高亮按文本节点分段渲染(此前的 v-html 逃生通道已删除)、无 eval / 远程代码、无遥测与埋点。
Header name validation with CRLF rejected, status codes clamped to 200–599, a 10 MB body cap (measured in UTF-8 bytes on the final outbound body, including one injected by a rule), rejected requests leaving a reason in the local log, ReDoS screening on user regexes, RE2 validation plus substitution bounds-checking for rules bound to the network layer, window.location.origin as the target origin for every cross-world message, cross-world proxy responses normalized at the bridge (failure envelopes get a requestId and a numeric status, and internal error text is never forwarded to the page), response-body path writes rejecting __proto__ / constructor / prototype keys, isTrustedSender on state-changing messages, boundary filtering for imported HAR/cURL/JSON (header maps on the import and capture path additionally drop invalid entries one by one), exports that strip credentials by default — Authorization / Cookie style headers, plus token-like query-parameter overrides on the config side, unticked only for a verbatim local backup — search highlighting rendered as text-node segments (the former v-html escape hatch is removed), no eval / remote code, and no telemetry.
上面这些基线里,ReDoS 筛查、导入规则过滤、响应体路径写入的原型链拒绝、跨 world 响应信封整形、查询参数编码保持、请求头与 CRLF 校验(isValidHeaderEntry / filterIncomingHeaders / validateRuleHeaders)、请求体上限(exceedsBodyCap)与状态码钳制(clampResponseStatus)已有直接的 Vitest 用例(tests/security-and-optimization.test.ts、tests/round2-regression.test.ts、tests/channel-consistency.test.ts、tests/proxyResponseGuard.test.ts、tests/requestGuard.test.ts、tests/highlight.test.ts、tests/exportSanitize.test.ts)。头名/CRLF 判据本轮从后台抽到 utils/headerValidation.ts,由规则表单的保存侧与导入侧共用,并新增 findInvalidHeaderNames(保存前逐项报出问题头名,只报名不回值)与 sanitizeImportedHeaderMap(导入路径逐条清洗)两个出口;exceedsBodyCap 与 clampResponseStatus 则是 entrypoints/background/proxyHandler.ts 导出的行为不变的可见性调整。isTrustedSender 与它守的那道 gate 由 tests/messageRouter.test.ts 直接对真实现断言(vi.stubGlobal + 动态 import,不比对实现副本):sender.url 必须是本扩展 URL 根的前缀——同前缀伪 ID、其他扩展 ID、非扩展协议、空 URL、把根串塞在 URL 中间都判不可信,且只认 url 一个字段;每一个状态修改类消息在不可信 sender 下都同步回 Unauthorized sender、不触达存储,且 gate 先于参数校验;任何消息类型在不可信 sender 下都不许产生写入(新加写 handler 却忘了登记进 gate 清单时靠这条兜住)。只读消息与 PROXY_REQUEST 不经 gate 是当前实现的既有事实,测试把它钉成契约:内容脚本的 sender.url 就是页面 URL,加 gate 会让代理与配置回放当场失效。本节此前记录的「isTrustedSender 无单测入口」一项已随之闭合;按上文「处理承诺」,其余未修复项的细节仍不在此公开。
Of these, ReDoS screening, imported-rule filtering, prototype-chain rejection in response-body path writes, cross-world response envelope normalization, query-parameter encoding fidelity, header/CRLF validation (isValidHeaderEntry / filterIncomingHeaders / validateRuleHeaders), the body cap (exceedsBodyCap) and status clamping (clampResponseStatus) all have direct Vitest cases (security-and-optimization, round2-regression, channel-consistency, proxyResponseGuard, requestGuard, highlight, exportSanitize). The header/CRLF predicates were lifted out of the background into utils/headerValidation.ts this round, so the rule form's save path and the import path share one set, and they gained two exports: findInvalidHeaderNames (lists offending header names before anything is saved, names only, never values) and sanitizeImportedHeaderMap (per-entry dropping on the import path). exceedsBodyCap and clampResponseStatus remain a visibility-only export from proxyHandler.ts. isTrustedSender and the gate it guards are asserted against the real implementation by tests/messageRouter.test.ts (vi.stubGlobal plus a dynamic import; nothing compares against a copy of the code): sender.url must start with this extension's URL root — same-prefix spoofed IDs, other extension IDs, non-extension schemes, empty URLs and a root string embedded mid-URL all count as untrusted, and url is the only field consulted; every state-changing message type answers an untrusted sender synchronously with Unauthorized sender, never reaches storage, and is gated before any argument validation; and no message type at all may produce a write for an untrusted sender, which is what catches a future write handler that forgot to register in the gate list. That read-only messages and PROXY_REQUEST bypass the gate is a fact of the current implementation, which the test pins as a contract: a content script's sender.url is the injected page's URL, so gating them would break proxying and config replay outright. The item previously recorded in this section — "isTrustedSender has no unit-test entry point" — is closed with this; per the disclosure commitment above, details of anything still unfixed stay undisclosed.