越界发现,记录于 #3610(零应用下 component/metadata/* 白屏)期间。按纪律只报不改,单独立单,未认领,留给 PM 分诊。
实测基线:origin/main @ 8d5418e5(packages/app-shell/src/console/AppContent.tsx)。
现象
三个伪路由开关里有两个是子串判断,而不是路径段判断:
:183 const isCreateAppRoute = location.pathname.endsWith('/create-app');
:184 const isSystemRoute = location.pathname.includes('/system');
:185 const isMetadataRoute = location.pathname.includes('/metadata');
includes('/system') 会命中 /system 开头的任意段,不止 system 这一段本身:/apps/x/system_log、/apps/x/system_setting、/apps/x/systems 全为真。isMetadataRoute 同理(metadata_import、metadata_log……)。
后果:恰好绕开「绝不渲染另一个 app」那道守卫
isSpecialRoute 为真会同时改变两处判断(:188、:198、:207):
:198 ((!appName || isSpecialRoute)
:199 ? (launcherApps.find(a => a.isDefault === true) || launcherApps[0])
:200 : undefined);
:207 const requestedAppMissing = !!appName && !matchedApp && !isSpecialRoute;
于是访问 /apps/拼错的app名/system_log:
matchedApp 为 undefined(app 名不存在);
isSystemRoute 被 system_log 误命中 → isSpecialRoute 为真;
requestedAppMissing 因此为 false,:547 的「App not available」守卫不触发;
activeApp 回退成 默认/第一个 app,渲染它的 shell,并把 system_log 当作该 app 下的 :objectName。
:193-200 的注释正是为了防住这件事(原文:"A normal unmatched appName must NOT silently render a DIFFERENT app — the readiness guard below shows loading / not-available instead. (Fixes 'the preview / nav renders the WRONG app right after building a new one'.)")。子串判断在这条路径上把它绕开了 —— 用户看到的是另一个 app 的界面,没有任何提示说所请求的 app 不存在。
为什么不在 #3610 里顺手修
#3610 的裁定是:子串判断把请求引进分支这件事是对的,错在分支服务不了(那已由 PR #3636 补齐路由 + catch-all 修掉)。本单是另一个方向的后果 —— 判断过宽导致 requestedAppMissing 失灵 —— 与 #3610 的白屏无因果关系,单独成立。#3610 的文件面也明确不含这三行。
可能的修法(留给分诊)
改成按路径段判断,而不是子串。例如把 pathname 切段后判断某一段是否 === 'system' / === 'metadata',或用 /\/system(\/|$)/ 这类带边界的正则。
⚠️ 改动前需要先测量清楚当前依赖这层宽松的调用点:/apps/x/system/marketplace/...、/apps/x/component/metadata/resource(段位置不在 :objectName 上)、宿主 extraRoutes 的 developer/*、docs/* 等,都要确认收紧后仍为真。这正是「消费端宽松兜底」的典型代价 —— 收紧是对的,但得先把真实的输入面量出来。
严重度自评不可靠,交分诊
触发前提是「app 名不匹配」+「首段以 system / metadata 开头」。平台内建对象普遍用 sys_ 前缀而非 system_,所以命中率取决于业务对象的命名,我无法从仓内判定。按 objectstack#4949 的纪律原样上报,不预判轻重。
关联
已就关键词(isSystemRoute / isMetadataRoute / isSpecialRoute / requestedAppMissing / 子串 / includes / wrong app)搜过本仓开放 issue 与 PR,无同源单。
Generated by Claude Code
越界发现,记录于 #3610(零应用下
component/metadata/*白屏)期间。按纪律只报不改,单独立单,未认领,留给 PM 分诊。实测基线:
origin/main@8d5418e5(packages/app-shell/src/console/AppContent.tsx)。现象
三个伪路由开关里有两个是子串判断,而不是路径段判断:
includes('/system')会命中/system开头的任意段,不止system这一段本身:/apps/x/system_log、/apps/x/system_setting、/apps/x/systems全为真。isMetadataRoute同理(metadata_import、metadata_log……)。后果:恰好绕开「绝不渲染另一个 app」那道守卫
isSpecialRoute为真会同时改变两处判断(:188、:198、:207):于是访问
/apps/拼错的app名/system_log:matchedApp为 undefined(app 名不存在);isSystemRoute被system_log误命中 →isSpecialRoute为真;requestedAppMissing因此为 false,:547的「App not available」守卫不触发;activeApp回退成 默认/第一个 app,渲染它的 shell,并把system_log当作该 app 下的:objectName。:193-200的注释正是为了防住这件事(原文:"A normal unmatched appName must NOT silently render a DIFFERENT app — the readiness guard below shows loading / not-available instead. (Fixes 'the preview / nav renders the WRONG app right after building a new one'.)")。子串判断在这条路径上把它绕开了 —— 用户看到的是另一个 app 的界面,没有任何提示说所请求的 app 不存在。为什么不在 #3610 里顺手修
#3610 的裁定是:子串判断把请求引进分支这件事是对的,错在分支服务不了(那已由 PR #3636 补齐路由 + catch-all 修掉)。本单是另一个方向的后果 —— 判断过宽导致
requestedAppMissing失灵 —— 与 #3610 的白屏无因果关系,单独成立。#3610 的文件面也明确不含这三行。可能的修法(留给分诊)
改成按路径段判断,而不是子串。例如把 pathname 切段后判断某一段是否
=== 'system'/=== 'metadata',或用/\/system(\/|$)/这类带边界的正则。/apps/x/system/marketplace/...、/apps/x/component/metadata/resource(段位置不在:objectName上)、宿主extraRoutes的developer/*、docs/*等,都要确认收紧后仍为真。这正是「消费端宽松兜底」的典型代价 —— 收紧是对的,但得先把真实的输入面量出来。严重度自评不可靠,交分诊
触发前提是「app 名不匹配」+「首段以
system/metadata开头」。平台内建对象普遍用sys_前缀而非system_,所以命中率取决于业务对象的命名,我无法从仓内判定。按 objectstack#4949 的纪律原样上报,不预判轻重。关联
isMetadataRoute判断的另一个后果(引进的分支无路由可服务),已修;本单是判断本身过宽的后果。已就关键词(
isSystemRoute/isMetadataRoute/isSpecialRoute/requestedAppMissing/ 子串 / includes / wrong app)搜过本仓开放 issue 与 PR,无同源单。Generated by Claude Code