强类型防坑:用 TypeScript 降低 OWASP 风险
把数据库模型直接丢给 API 接口,或者把一个用户 ID 误传给租户 ID 参数,这种低级错误在大型项目里太常见了。虽然框架自带的安全检查很重要,但如果能在编译阶段就让代码“写不下去”错误逻辑,效率会高得多。
下一篇
George Hotz在AMD AI大会上的观点拆解 →
强类型不是万能药,它解决不了权限校验或 HTML 过滤,但能通过定义“边界”来减少漏洞。
1. 强制使用 DTO 隔离敏感数据
最怕的就是直接 res.json(user),万一哪天数据库加了个 password_hash 字段,分分钟全部泄露。建议定义严格的 DTO(数据传输对象)和映射函数。
type UserRow = {
id: string;
email: string;
passwordHash: string;
mfaSecret: string | null;
internalNotes: string | null;
};
type PublicUserDto = {
id: string;
email: string;
};
function toPublicUser(user: UserRow): PublicUserDto {
return { id: user.id, email: user.email };
}
// 永远不要在 HTTP 边界直接序列化 UserRow2. 用 Branded Types 防止 ID 混淆
在 TS 里,所有的 ID 都是 string,这导致你很容易把 userId 传给需要 tenantId 的函数。可以用品牌类型(Branded Types)给字符串“打标签”。
declare const tenantBrand: unique symbol;
declare const userBrand: unique symbol;
type TenantId = string & { readonly [tenantBrand]: true };
type UserId = string & { readonly [userBrand]: true };
function loadUser(tenantId: TenantId, userId: UserId) {
// 这样如果传反了,编译器直接报错,不用等到运行时才发现数据不对
}3. 显式化权限上下文
不要在函数里传 isAdmin: boolean 这种模糊的参数,试着把权限状态定义为具体的类型。
type Viewer = { kind: "viewer"; userId: UserId };
type ProjectEditor = { kind: "project-editor"; userId: UserId; projectId: string };
type ProjectAccess = Viewer | ProjectEditor;
function renameProject(access: ProjectEditor, name: string) {
// 只有持有 ProjectEditor 类型的对象才能调用此函数
}这种做法本质上是把安全意图编码进了类型系统。不过得注意,Branded Types 在运行时会消失,所以外部输入在进入系统时必须经过严格的验证和类型转换。
