yarrumretep/ts-prefix-internals
GitHub: yarrumretep/ts-prefix-internals
一个利用 TypeScript 类型信息为内部符号添加前缀的 CLI 工具,使压缩器能够安全地混淆属性名,从而减小生产包体积并增加逆向难度。
Stars: 1 | Forks: 0
# ts-prefix-internals
[](https://github.com/yarrumretep/ts-prefix-internals/actions/workflows/ci.yml)
一个 TypeScript CLI 工具,它为内部符号添加 `_` 前缀,以便 terser 或 esbuild 可以通过 `mangleProps: /^_/` 激进地混淆它们。
## 问题所在
像 terser 和 esbuild 这样的 JavaScript 压缩工具可以混淆局部变量名,但它们**无法安全地混淆属性和方法名**。它们无法知道哪些名称是内部实现细节,哪些是公共 API 或 DOM/库接口的一部分。混淆错误的属性名会在运行时破坏你的代码。
这意味着类属性、方法名和其他成员标识符在你的生产包中保持未混淆状态,这通常占标准压缩后剩余代码大小的很大一部分。
## 解决方案
TypeScript 的编译器 API *确实*知道哪些符号是内部的。它拥有完整的类型信息,了解导出、类可见性修饰符以及整个依赖图。
`ts-prefix-internals` 利用这些信息来:
1. 从桶导出入口点**发现你的公共 API 表面**
2. 将项目中的**每个符号分类**为公共或内部
3. 使用 TypeScript Language Service 进行安全的跨文件重命名,**为内部符号添加 `_` 前缀**
4. **验证输出**编译无错误
添加前缀后,你配置压缩器混淆所有匹配 `/^_/` 的内容:
```
// terser
{
mangle: {
properties: { regex: /^_/ }
}
}
// esbuild
{
mangleProps: /^_/
}
```
## 会被添加前缀的内容
**内部(将被添加前缀):**
- 未从入口点导出的类、函数和变量
- 导出类的私有成员
- 非导出类的所有成员
- 内部接口、类型别名和枚举
**公共 API(将不会被添加前缀):**
- 从入口点文件导出的符号
- 导出类的公共/受保护成员
- 导出接口和枚举的成员
- 在公共 API 签名中引用的类型(递归跟踪)
- 来自 `node_modules` 或 `.d.ts` 文件的任何内容
- 已经以 `_` 开头的符号
## 安装
```
npm install -D ts-prefix-internals
```
## 使用方法
### CLI
```
# 预览重命名内容
npx ts-prefix-internals -p tsconfig.json -e src/index.ts -o .prefixed --dry-run
# 重命名并输出
npx ts-prefix-internals -p tsconfig.json -e src/index.ts -o .prefixed
# 多入口点
npx ts-prefix-internals -p tsconfig.json -e src/index.ts -e src/server.ts -o .prefixed
```
### 选项
```
-p, --project Path to tsconfig.json (required)
-e, --entry Public API entry point file(s), repeatable (required)
-o, --outDir Output directory for rewritten files (required)
--prefix Prefix string (default: "_")
--dry-run Report what would be renamed without writing files
--verbose Print every rename decision with reasoning
--skip-validation Skip post-rename type-check
--force Continue despite dynamic-access errors (exit 0)
-h, --help Show help
```
### 编程 API
```
import { prefixInternals } from 'ts-prefix-internals';
const result = await prefixInternals({
projectPath: 'tsconfig.json',
entryPoints: ['src/index.ts'],
outDir: '.prefixed',
prefix: '_',
dryRun: false,
verbose: false,
skipValidation: false,
});
console.log(`Prefixed ${result.willPrefix.length} symbols`);
console.log(`Kept ${result.willNotPrefix.length} public API symbols`);
if (result.validationErrors) {
console.error('Output has type errors:', result.validationErrors);
}
```
## 示例
给定一个具有桶导出的项目:
```
// src/index.ts
export { Processor } from './engine';
export { Coord, CoordKind } from './types';
```
以及一个未从桶中导出的内部类:
```
// src/graph.ts
export class LinkMap {
private forward: Map>;
connect(a: string, b: string): void { /* ... */ }
}
```
运行该工具会产生:
```
WILL PREFIX (internal):
LinkMap class graph.ts:1 -> _LinkMap
LinkMap.forward property graph.ts:2 -> _forward
LinkMap.connect method graph.ts:4 -> _connect
Processor.links property engine.ts:5 -> _links
Processor.pending property engine.ts:6 -> _pending
WILL NOT PREFIX (public API):
Processor class engine.ts:4 (exported)
Processor.setEntry method engine.ts:15 (public member)
Coord interface types.ts:1 (exported)
Coord.ns property types.ts:2 (interface member)
```
输出目录包含有效的 TypeScript,其中所有内部符号都已添加前缀,可以进行激进的压缩。
## 工作原理
1. **API 表面发现** (`api-surface.ts`) -- 从入口点桶导出开始,递归发现每个公共符号。解析别名链 (`export { Foo } from './foo'`),遍历类型签名以查找引用的类型,包括公共/受保护的类成员、接口成员和枚举成员。
2. **符号分类** (`classifier.ts`) -- 遍历每个源文件并对每个符号进行分类。使用公共 API 集来确定什么是内部的。处理私有类成员、构造函数参数属性,并为动态属性访问生成警告。
3. **重命名** (`renamer.ts`) -- 使用 TypeScript Language Service `findRenameLocations` API 进行安全的跨文件重命名。首先收集所有编辑,去重,按逆序排序,并自下而上应用以避免位置偏移。
4. **验证** -- 使用 `tsc --noEmit` 编译输出以验证重命名是安全的。
## 构建流水线集成
使用此工具的典型构建流水线:
```
# 1. Prefix internal symbols
npx ts-prefix-internals -p tsconfig.json -e src/index.ts -o .prefixed
# 2. Compile the prefixed source
cd .prefixed && tsc
# 3. Bundle and mangle with terser/esbuild
esbuild .prefixed/dist/index.js --bundle --minify --mangle-props=_
```
或者作为 package.json 脚本:
```
{
"scripts": {
"prefix": "ts-prefix-internals -p tsconfig.json -e src/index.ts -o .prefixed",
"build": "npm run prefix && cd .prefixed && tsc && esbuild dist/index.js --bundle --minify --mangle-props=_"
}
}
```
## 发布
```
# patch release (默认)
npm run release
# minor/major/prerelease
npm run release -- minor
```
发布脚本行为:
1. 验证你在 `main` 分支上且工作树是干净的
2. 运行测试
3. 使用 `npm version` 提升版本
4. 推送分支 + 标签
发布行为:
- 匹配 `v*` 的标签推送触发 `.github/workflows/publish.yml`
- 工作流构建/测试,验证标签版本与 `package.json` 匹配,发布到 npm,并创建 GitHub Release
- 预发布版本(例如 `1.2.0-beta.1`)使用 npm dist-tag `next` 发布
一次性设置:
1. 在 npm 中,为此 GitHub 仓库和工作流文件配置 Trusted Publisher
2. 在 GitHub 中,创建环境 `npm-release` 并可选地要求审查者/限制标签
3. 使用 GitHub ruleset 保护发布标签(例如 `v*`),以便只有维护者可以创建它们
## 安全性
- 使用 TypeScript Language Service 进行重命名(而不是正则表达式/文本替换)
- 验证输出在重命名后可以编译
- 从不修改来自 `node_modules` 或 `.d.ts` 文件的符号
- 跳过带有装饰器的符号(装饰器名称反射会破坏)
- 通过三层诊断检测动态属性访问:
- **Error**: 使用与前缀属性匹配的字符串字面量类型的括号访问(已证实会损坏)
- **Warn**: 使用宽泛 `string` 或 `any` 类型的括号访问(可能会损坏)
- **Silent**: 数组/元组索引(安全,已抑制)
- 跳过已经以 `_` 开头的符号
## 要求
- Node.js >= 18
- TypeScript >= 5.0(作为项目依赖)
## 许可证
MIT
标签:AST操作, CMS安全, DNS 反向解析, esbuild, JavaScript, MITM代理, Terser, TypeScript, 代码优化, 代码压缩, 代码混淆, 体积优化, 内部符号, 前端工程化, 安全插件, 属性混淆, 文档结构分析, 符号重命名, 编译器API, 自动化攻击, 自动化攻击