Skip to content

FNOS-017 fnOS 插件路由 403 误拒修复 ​

项目内容
需求编号FNOS-017
提出日期2026-10-18
需求状态已通过真机验收
关联计划PLAN-FNOS-017 fnOS 插件路由 403 误拒修复
影响插件@tnnevol/dsh-fnos
关联需求FNOS-014(当前运行时基线)

需求背景 ​

用户报告:经 https://<设备>.fnos.net 访问时,fnOS 插件的授权目录接口返回 403,响应体为 {"error":"remote-web-origin-not-trusted"}。受影响的路径包括:

text
/plugins/dsh-fnos/authorized-directories/entries

该接口是 /fn 文件引用、授权目录管理与工作区授权快捷入口的数据来源。403 之后这些功能在远程访问下不可用,而同一台设备经内网直连时正常——这是排查的关键差异。

插件对全部 fnOS 路由统一施加一层同源校验(isTrustedFnosRequest),不通过即 403。判定逻辑为:

ts
const fetchSite = header(req, 'sec-fetch-site')?.trim().toLowerCase()
if (fetchSite === 'cross-site') return false
const origin = header(req, 'origin')
if (origin !== undefined) return requestOrigin(req) === normalizeOrigin(origin)
return localPeer(req)

其中 requestOrigin 的取值优先级是 x-forwarded-host → host,协议同理(x-forwarded-proto → socket 加密状态)。

调研结论 ​

结论出处影响的功能状态
403 由插件的同源校验发出,不是 DSH 宿主或 fnOS 网关plugins/dsh-fnos-plugin/src/host/authorized-directories.ts 的 isTrustedFnosRequest 与 authorize()FNOS-017-01已核实
网关会改写转发给插件的 host 与 origin,把它们统一成 loopback 权威(127.0.0.1:<端口>),并移除 sec-fetch-site 之外的浏览器来源线索packages/fnos-gateway/src/middleware/request-headers.ts 的 applyLoopbackHeaders / applyProxyRequestHeadersFNOS-017-01已核实
网关不设置任何 x-forwarded-*;本仓库全部源码与文档中零处写入该头全仓库检索 x-forwardedFNOS-017-01已核实
因此 x-forwarded-host 只可能来自 fnOS 平台侧或用户自建反代,对插件而言是外部输入;而 requestOrigin 无条件信任它同上两条的合成FNOS-017-01已核实
两处取值来源不一致即 403:requestOrigin 用 x-forwarded-host(公网域名、https),normalizeOrigin(origin) 用网关改写后的 loopback(http)。两者永远不相等本地仿真复现(见下)FNOS-017-01已核实
另有两种真 403 场景,均属预期拒绝、不应改动:中间层把 sec-fetch-site 标成 cross-site;请求无 origin 且来源 IP 不是回环同仿真FNOS-017-02已核实
现有用例只覆盖「loopback 直通」与「路由内部拒绝封装」,没有覆盖带转发头的同源场景,因此该缺陷长期未被测试发现tests/host/authorized-directories-persist-route.spec.ts 的 fakeRequest:只设 host: 'nas.local:5666' 与 remoteAddress: '127.0.0.1'FNOS-017-03已核实

本地仿真结果(三种来源组合下 isTrustedFnosRequest 的判定):

场景转发头判定
网关正常改写,无转发头host/origin 均为 loopback通过
存在 x-forwarded-host=公网域名origin 仍为 loopback403:requestOrigin=https://… 与 origin=http://127.0.0.1:… 不相等
中间层标 sec-fetch-site: cross-site—403(预期)
无 origin 且来源非回环—403(预期)
mermaid
sequenceDiagram
    participant B as 浏览器(https://设备.fnos.net)
    participant P as fnOS 平台反代
    participant G as fnOS 网关
    participant D as DSH + dsh-fnos 插件
    B->>P: GET /plugins/dsh-fnos/authorized-directories/entries
    P->>G: 转发,附 x-forwarded-host=设备.fnos.net、x-forwarded-proto=https
    G->>D: host=127.0.0.1:port、origin 改写为 http://127.0.0.1:port
    Note over D: requestOrigin 取 x-forwarded-host → https://设备.fnos.net<br/>normalizeOrigin(origin) → http://127.0.0.1:port<br/>变更点:两读来源不同 ⇒ 不等 ⇒ 403
    D-->>B: 403 remote-web-origin-not-trusted

需求目标 ​

  • 经 fnOS 远程域名访问时,fnOS 插件的同源接口不再被误判为跨站,功能恢复可用。
  • 跨站拒绝能力不得放松:伪造 Origin、Sec-Fetch-Site: cross-site、非回环来源且无来源标识的请求必须继续被拒绝。
  • 判定依据收敛到「网关实际转发的那一套头」,不再让来源不同的两组值互相比较。

功能列表 ​

编号优先级功能用户可观察结果状态
FNOS-017-01P0远程访问下 fnOS 路由可用经 https://<设备>.fnos.net 打开 DSH 时,授权目录列表、/fn 文件引用、工作区授权入口正常工作,不再 403真机待验
FNOS-017-02P0跨站拒绝不放松伪造 Origin 或标 cross-site 的请求仍返回 403已验(三类拒绝均有效)
FNOS-017-03P1回归覆盖补齐带 x-forwarded-* 的同源请求、跨站请求、无来源标识请求三类场景均有自动用例已验(修复前失败、修复后通过)
FNOS-017-04P1内网直连不回归内网直连(loopback 直通路径)继续正常,行为与修复前一致已验(内网未回归)

验收环境范围 ​

  • FNOS-017-01、FNOS-017-04 在真实 fnOS NAS远程访问(https://<设备>.fnos.net)验收——这是缺陷的原始场景,无法在本地复现。
  • FNOS-017-02、FNOS-017-03 在本地构测环境验收(单元用例可完整覆盖)。
  • DSH Web 内网访问作为对照组,用于确认修复前后内网行为不变。

行为约束 ​

  • 信任判定的唯一事实来源是网关实际转发给插件的头;不得为了放行而新增对任意入站头的信任。
  • 保留 sec-fetch-site: cross-site 的直接拒绝——它是最强、最不可绕过的跨站信号。
  • 修复不得放宽到「凡带 x-forwarded-host 就放行」:那会让任何能设置该头的中间层绕过同源校验。
  • 不改变任何路由的路径、请求体与响应格式;不涉及授权目录本身的数据结构与权限语义。

不在本次范围内 ​

  • fnOS 平台或网关侧转发头行为的调整(属外部系统)。
  • DSH 宿主自身的同源策略与 webServer 路由框架。
  • 授权目录的 ACL 判定、路径校验与语义名解析。
  • 其它插件是否也存在同类判定(如发现同类问题另立需求)。

既有功能变更关系 ​

本次是缺陷修复,不新增用户可见功能。它恢复 FNOS-014 基线之下、经远程域名访问时被误拒的既有能力(FNOS-001/008 建立的授权目录与 /fn 引用)。修复后这些功能在远程与内网两种访问方式下行为一致。

mermaid
stateDiagram-v2
    [*] --> Remote403: 远程访问(缺陷态)
    [*] --> LocalOk: 内网直连(正常)
    Remote403 --> BothOk: 变更点:判定来源收敛(FNOS-017-01)
    LocalOk --> BothOk
    BothOk --> [*]

用户可观察结果:远程访问从「授权目录区空白 / /fn 不可用」变为与内网一致;跨站伪造请求仍被拒绝。

验收条件与完成状态 ​

FNOS-017-01 ​

  • FNOS-017-01-AC-01:经 https://<设备>.fnos.net 访问,/plugins/dsh-fnos/authorized-directories/entries 返回 200 且列出已授权目录。
  • FNOS-017-01-AC-02:同一会话内 /fn 文件引用可列出条目并完成一次引用插入。
  • FNOS-017-01-AC-03:插件详情页授权目录区块可正常加载与展示。

FNOS-017-02 ​

  • FNOS-017-02-AC-01:Origin 为外部域名(与请求权威不一致)时返回 403。
  • FNOS-017-02-AC-02:Sec-Fetch-Site: cross-site 时返回 403,即使 Origin 看似合法。
  • FNOS-017-02-AC-03:无 Origin 且来源 IP 非回环时返回 403。

FNOS-017-03 ​

  • FNOS-017-03-AC-01:存在自动用例覆盖「带 x-forwarded-* 的同源请求被放行」。
  • FNOS-017-03-AC-02:上述三类拒绝场景各有自动用例,且用例在修复前会失败。

FNOS-017-04 ​

  • FNOS-017-04-AC-01:内网直连下授权目录、/fn 引用与修复前行为一致,无新增 403。

变更记录 ​

日期变更说明
2026-10-18初始登记建立 FNOS-017。根因已定位:requestOrigin 以 x-forwarded-host 优先,而网关把 origin 改写为 loopback,两侧来源不同导致必然不等。7 条调研结论、三种 403 场景的本地仿真数据已登记。关联 PLAN-FNOS-017。
2026-10-18修复完成requestOrigin 改为只读网关写入的 host,x-forwarded-* 不再参与同源比较;新增 8 条用例(含转发头列表形态与 host 缺失兜底)。已确认新增用例在旧实现上失败 2 条、修复后全绿,插件 33 个测试文件 238 条用例通过。真机验收待执行。

基于 VitePress 构建