assetlinks.json 被放在了错误的路径上
发布于
2026-10-06,dev.to 账号 daniel_pertu 发表了一篇关于 CogniPrep 的建设报告。报告把
CogniPrep 描述为一个用于练习雇主评测的 Next.js 应用,而这篇报告的主题是:用于证明该应用
拥有自己域名的那个文件,被从错误的 URL 提供出去了。报告写明
cogniprep.app/.well-known/assetlinks.json 返回 404,而
cogniprep.app/api/.well-known/assetlinks.json 返回 200,带
content-type: application/json 和真实内容,也就是说 JSON 本身是对的,错的只有它的地址
(该报告)。
这篇报告查到了什么
Section titled “这篇报告查到了什么”报告指出,根路径文件缺失这一配置会导致验证失败;报告还检查了该应用的 manifest,以及那个 Android 构建如何向移动浏览器声明。 下面按顺序逐条列出,用的是报告本身以及它所涉及的平台文档的措辞 (该报告)。
Android 只会取的那一个路径
Section titled “Android 只会取的那一个路径”报告写明 CogniPrep 首先作为网站发布,另有一个以 Trusted Web Activity 构建的 Android 外壳,
报告把它描述为 Chrome 在不自备 WebView 的情况下把 Web 应用放进 Play Store 的方式。报告写明
Trusted Web Activity 会以全屏、无地址栏的方式运行该站点,但前提是它能证明这个 Android 包与
这个域名属于同一个所有者;而这份证明就是 Digital Asset Links:一个通过 HTTPS 取回的 JSON
文件,只来自一个路径,报告把它写作
https://<your-domain>/.well-known/assetlinks.json
(该报告)。
Google 自己的 Digital Asset Links 文档用更严格的措辞说明了同一个位置。该页把这套协议与 API
描述为让一个应用或网站能对其他应用或网站作出公开、可验证的声明,并说声明被包含在一个
statement list 中,该 statement list 是明文且可公开访问的,位于由 principal 控制、且难以
伪造或篡改的位置。在它的用法示例中,网站把 statement list 发布在
https://www.example.com/.well-known/assetlinks.json,并且该页写明:这是站点上 statement
list 的官方名称与位置,位于任何其他位置、或使用任何其他名称的 statement list,对该站点都是
无效的。示例中的那条声明授予该 Android 应用打开本站链接的权限,relation 为
delegate_permission/common.handle_all_urls,target 给出 android_app 命名空间、一个
package_name 以及 sha256_cert_fingerprints,该页把后者描述为应用签名证书的 SHA256
指纹
(Google,Digital Asset Links 入门)。
报告印出的那个 route handler 与这一形状吻合。它返回一个字面量,其中
relation: ['delegate_permission/common.handle_all_urls'],target 给出
namespace: 'android_app'、package_name: 'app.cogniprep.twa' 以及一个
sha256_cert_fingerprints 数组。报告自己对它的判断是:relation 对、命名空间对、包名对、
是一个真实的签名证书指纹,但从错误的 URL 提供
(该报告)。
这个文件为什么落到了 /api 下面
Section titled “这个文件为什么落到了 /api 下面”报告写明该文件位于 app/api/.well-known/assetlinks.json/route.ts,App Router 把目录映射为
URL 段,并且完全接受名称中带点的目录名,于是 .well-known 是一段,assetlinks.json 是一段,
得到的路由就是 /api/.well-known/assetlinks.json。报告的结论是:没有任何东西出错,Next.js
正是按照这个目录结构所要求的那样做的
(该报告)。
报告把错误定位在该路径最前面的那个 api/。它写明应用里其他每一个服务端端点都位于
app/api/ 之下,因为端点就该放那里;而一个从 GET handler 返回 JSON 的文件读起来像端点,
实际上并不是:它是一个恰好由代码生成的静态 well-known 资源,而消费它的东西并不是前端。报告
写明 Android 的验证服务没有配置项、没有 base path、也没有覆盖项,它要么取
/.well-known/assetlinks.json,要么失败
(该报告)。
报告给出的修法里,每一种都被描述为一处改动。第一种是把目录移到
app/.well-known/assetlinks.json/route.ts,理由是路由路径就是目录路径。第二种是让 handler
留在原处,在 next.config.mjs 里加一条 rewrites(),把
/.well-known/assetlinks.json 映射到 /api/.well-known/assetlinks.json。报告倾向于移动,
给出的理由是:rewrite 是第二处必须持续保持为真的地方,而这个文件身上没有任何 API 的性质
(该报告)。
构建流程为什么一声都没出
Section titled “构建流程为什么一声都没出”报告写明缺失的 assetlinks.json 不会抛错、不会打日志、也不会 500,Web 应用完全不受影响,
每个浏览器访客拿到的还是一直以来的那个站点。报告写明唯一的症状出现在 Android 设备上:一个
验证失败的 Trusted Web Activity 不会崩溃,而是降级,把站点开在一个显示着 URL 栏的 Custom
Tab 里,于是应用仍然可用,只是不再像一个应用
(该报告)。
Chrome 的 Trusted Web Activity 快速上手文档把这种回退记录为设计行为,而不是这个应用的怪癖。
该页写明 Trusted Web Activity 可以启动一个没有任何浏览器 UI 的全屏浏览器标签页,这项能力被
限制在你拥有的网站上,而所有权通过设置 Digital Asset Links 来证明。该页写明启动 Trusted Web
Activity 时浏览器会验证 Digital Asset Links,若验证失败,浏览器会回退为以 Custom Tab 显示该
网站;该页在另一处把这同一结果描述为以页面顶部带浏览器 UI 的 Custom Tab 启动网站。同一页还
要求在创建 assetlinks.json 之后,把它上传到网站上相对于根目录的
.well-known/assetlinks.json,并点名使用了错误的签名是验证失败的一个常见原因
(Chrome,Trusted Web Activity 快速上手)。
报告还补充说,该应用自己的抓取规则把能用的那份副本藏了起来。它写明 app/robots.ts 发出
Disallow: /api/ 与 Disallow: /*.json,于是唯一真正提供该文件的那个 URL 被排除了两次,
一次按前缀、一次按扩展名,并写明发出的文件里有 740 行 Disallow。报告写明 Android 不读
robots.txt,所以这并不是失败的原因,但这也意味着对该站点的任何抓取都不会呈现能用的那份副本,
也不会注意到缺失的那份。它还指出一条已经失效的规则 Disallow: /manifest.ts,并说它什么也挡
不住,因为 app/manifest.ts 是 Next.js 的文件约定,它产生的 URL 是
/manifest.webmanifest,而一条针对源文件名写的规则无法匹配请求路径
(该报告)。
manifest 里的其他发现
Section titled “manifest 里的其他发现”报告印出了该应用的 icon 数组,其中 /logo-light.png 以 512x512 和 image/png 声明了两次,
一次带 "purpose": "any",一次带 "purpose": "maskable"。报告写明 any 会被原样绘制,
maskable 会被绘制进启动器想要的任何形状,并且需要四边各约 20% 的留白才能在裁切中存活;而把
同一个方形 logo 同时用于两者,意味着 maskable 这条路径在裁切一幅从未为此排版的图稿。报告的
判断是:这样列出并不算错,但它是一项关于图像的声明,而不是 manifest 的一项属性,并且没有任何
东西会替你校验它
(该报告)。
MDN 记录了作出该声明所用的那个成员。MDN 把 purpose 描述为一个大小写敏感的关键字字符串,用
于指定浏览器或操作系统可以在哪一个或哪几个上下文中使用该图标,并写明浏览器把这些值当作提示,
用来判断图标在何处以及如何显示;MDN 把 maskable 定义为表示该图标在设计时已考虑图标蒙版与
安全区,使得图像在安全区之外的任何部分都可以被忽略并被遮掉。MDN 把 any 定义为表示该图标可
在任何上下文中使用,并写明这是默认值
(MDN,icons)。
manifest 的第二处发现是名称。报告写明 manifest 里写的是
CogniPrep - Arctic Shores Practice,并描述了 “all 14 Arctic Shores psychometric games”,
写这句话的时候它是真的,而该应用如今覆盖数十家评测供应商。报告写明已安装应用的主屏幕标签是
一个网站本身从不渲染的字符串,所以看站点永远看不出它已经漂移。报告还写明不存在
related_applications 条目,于是访问该站点的移动浏览器无从知道那个 Android 构建的存在
(该报告)。
MDN 把 related_applications 描述为用于指定一个或多个与该 Web 应用相关的应用的 manifest
成员,这些应用可以是平台专属应用,也可以是 Progressive Web App;并写明它让
Navigator.getInstalledRelatedApps() 这类 Web API 得以检查该 Web 应用的平台专属版本、或该
Web 应用本身是否已经安装在设备上。MDN 在示例 platform 值中列出了 play,并把该成员标记为
实验性
(MDN,related_applications)。
对 PWA 开发者为什么重要
Section titled “对 PWA 开发者为什么重要”报告点出了它的多处发现共享的一种形状:一个只会被浏览器之外的东西消费的值。报告写明验证
文件由 Android 读取,manifest 的 name 由启动器读取,maskable 这一 purpose 由开发者并不
控制的图标蒙版来兑现;而它们都不会出现在页面上,都不会让类型检查失败,也都不会被任何渲染该
应用的测试覆盖。报告给出的后果是:一个返回 200 且 JSON 正确的 route handler 让人感觉已经做完,
而要发现它是在错误的地址上做完的,唯一办法是去取消费方真正使用的那个地址;为此它给出
curl https://yourdomain.com/.well-known/assetlinks.json
(该报告)。
平台文档从消费方这一侧说出了同一件事。Google 写明 statement list 的价值来自它位于由 principal 控制的位置:该协议并不认证作出声明的 principal,但该声明位于一个与 principal 强 关联、且处于 principal 控制之下的特定位置。一个 principal 为自己方便而选的路径不是那个位置, 而 Google 的页面直接写明:位于任何其他位置、或使用任何其他名称的 statement list,对该站点都是 无效的 (Google,Digital Asset Links 入门)。
Chrome 的快速上手文档提供了用于区分 Trusted Web Activity 两种失败方式的诊断手段。该页写明
Trusted Web Activity 会尽量遵循用户默认选择的浏览器:如果用户的默认浏览器支持 Trusted Web
Activity,就会启动它;否则,任何已安装且支持的浏览器会被选中;最后,默认行为是回退到 Custom
Tabs 模式。正因如此,该页要求在调试 Trusted Web Activity 时确认当前实际使用的是哪个浏览器,
并印出 adb logcat -v brief | grep -e TWAProviderPicker 作为查看方式
(Chrome,Trusted Web Activity 快速上手)。
站内相关条目
Section titled “站内相关条目”- Capabilities:可沿此交叉链接继续阅读,并对照报告中对 Trusted Web Activity 集成的讨论。
- Storage:可沿此交叉链接继续阅读,并对照报告中对固定相对源路径上的 well-known 文件的讨论。
- Performance:报告写明它的 404 是作为一整页 HTML 提供的,带一张 样式表和约十二个脚本标签;可沿此交叉链接继续阅读并对照这一发现。
待核实与存疑之处
Section titled “待核实与存疑之处”- 缺失的
/.well-known/assetlinks.json是否是 CogniPrep 的 Trusted Web Activity 验证失败的 唯一原因,而非其中之一,未经确认。Chrome 的快速上手文档把错误的签名签章点名为同一种失败的 一个常见原因。 - Android 包
app.cogniprep.twa是否由 route handler 所返回指纹对应的那张证书签名,未经确认; 报告印出的指纹是截断的。 - 把 route handler 移到
app/.well-known/assetlinks.json/route.ts之后,验证是否会在某个特定 浏览器与 Android 版本上成功,报告并未说明。 - 报告归因于 maskable 路径的四边各约 20% 留白,是否对应某个具体启动器所应用的安全区,未经确认。
- 加上一条
related_applications条目是否会改变移动浏览器对该站点的呈现,未经确认;MDN 把该 成员标记为实验性。 - 报告在其发出的 robots.txt 中数出的 740 行
Disallow是否除抓取之外还影响别的什么,未被涉及。
- Our Android verification file is served from /api, and Android only ever looks in /.well-known(dev.to)
- Digital Asset Links: Getting Started(developers.google.com)
- Trusted Web Activity quick start guide(developer.chrome.com)
- icons(developer.mozilla.org)
- related_applications(developer.mozilla.org)