# 阻止对已发布 JavaScript 中付费墙的绕过

打包产物中的付费墙判断是一个返回布尔值的具名函数，翻转它只需一行补丁。混淆与逐构建变化的种子如何改变查找它和让补丁保持有效这两件事。

Source: https://www.afterpack.dev/zh/docs/use-cases/paywall-checks

在浏览器中运行的付费墙或权益检查,只有在你让它变得如此的情况下,查找和打补丁的成本才会很高。压缩后的输出两者都做不到。

## 为什么付费墙判断只需一行补丁

在普通或压缩后的输出中,付费墙判断通常是一个返回布尔值的具名函数。任何阅读打包产物的人都可以搜索为它命名的那个词:`isSubscribed`、`hasAccess`、`checkEntitlement`。一旦找到,最廉价的攻击方式就是用一行补丁翻转返回值。

这个补丁在每个用户的文件副本上都有效,并且只要文件形态不变,它在你下一次发布时依然有效。查找判断逻辑的工作只需付出一次,之后每次构建都可以复用。

## 构建之后,已发布的文件是什么样子

默认构建会重命名标识符并编码字符串字面量,因此原本叫 `isSubscribed` 的函数不再带有那个名字,它周围的字符串也不再是可读文本。在已发布的文件中搜索那个名字或那条消息,什么补丁点都找不到。

这里最重要的属性是逐构建的多态性。[种子](https://www.afterpack.dev/zh/docs/config#seed)在每次构建时都是新的,除非你固定它,因此针对某一次发布写的补丁不会对齐到下一次。一旦攻击者摸清判断逻辑所在的位置,你每次发布都会让他重新付出这个成本。

## 提高判断逻辑的保护强度

默认预设已经在较低的 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity) 目标下做了重命名和编码。要把整个打包产物提高一档:

```bash
npx afterpack@latest dist/ --preset=hard
```

要只提高判断函数本身、不触碰打包产物的其余部分,用指令标记它:

```ts
/* @afterpack preset=hard */
function checkEntitlement(token: string): boolean {
  return validateSubscription(token);
}
/* @afterpack end */
```

[指令](https://www.afterpack.dev/zh/docs/directives)在 [Pro](https://www.afterpack.dev/zh/docs/tiers) 构建中生效。

## 检查判断逻辑是否得到了保护

构建之后打开[Protection Map](https://www.afterpack.dev/zh/docs/protection-map#how-to-read-it),找到判断函数。它的颜色应该比周围代码更深,反映出你为该区域设置的更高 `complexity` 目标。如果它看起来和打包产物的其余部分一样,读一下该次构建的[诊断信息](https://www.afterpack.dev/zh/docs/diagnostics):被读取到但没有产生效果的指令会记录在那里。

## 客户端判断逻辑的边界在哪里

逐构建轮换才是这里真正持久有效的部分。每次发布都有新种子,意味着定位和修补判断逻辑的工作永远无法延续到下一次构建,因此针对某一次构建写的补丁、脚本或用户脚本,在下一次构建上就会失效。一个逐构建分析单次构建的静态读取者,依然可以还原出一段等价的计算,因为判断逻辑必须能在浏览器里正确运行,所以对于任何关系到收入的门槛,都要把客户端检查和服务端的权益校验搭配使用。

## 下一步

- [分区域混淆指令](https://www.afterpack.dev/zh/docs/directives):完整的指令语法,以及还有哪些内容值得标记。
- [阻止用户脚本和扩展修补你的 Web 应用](https://www.afterpack.dev/zh/docs/use-cases/patching-tools):结构性变化和新种子会对文本匹配式补丁造成什么影响。
- [威胁模型](https://www.afterpack.dev/zh/docs/threat-model):静态读取者能还原什么,不能还原什么。
