new.target 是运行时上下文提示工具,非安全边界;真正防护需三层配合:new.target 语义校验、显式返回可控实例、运行时指纹绑定。

直接用 new.target 无法“完美防止”构造函数被恶意劫持——它本身不是安全边界,而是运行时上下文提示工具。真正能提升防护水位的,是结合 new.target 的语义校验 + 构造结果可控返回 + 实例状态隔离这三层配合。
用 new.target 锁定合法调用入口
在加密流类(如 class AESStream)构造器开头强制校验:
若 !new.target,说明被当普通函数调用,立即抛错;
若 new.target !== AESStream,说明正被继承或代理,可选择拒绝(除非你明确支持子类化)。
- 避免写成
this.constructor === AESStream——它易被篡改,而new.target是只读元属性,不可赋值、不可 delete - 不依赖
instanceof或constructor.name做判断,它们在动态构造、Proxy 包裹等场景下不可靠
用显式 return 劫持实例,切断原型链暴露面
即使攻击者绕过 new 调用检查(比如通过 Reflect.construct),只要你在构造器中显式返回一个干净对象,就能阻断原始实例的属性和方法泄露:
- 返回一个空对象
{}或Object.create(null),它不继承AESStream.prototype,没有原型方法可枚举 - 若需保留部分能力,返回
Proxy实例,对get/has拦截所有访问,敏感字段(如密钥、缓冲区)一律屏蔽 - ⚠️ 注意:返回对象后,
instanceof AESStream会失效——这反而是好事,说明外部无法通过类型检测反推内部结构
在实例内部做运行时指纹绑定
仅靠构造时检查不够。可在构造器中为合法实例注入不可伪造的运行时标识:
- 生成一次性符号
const token = Symbol('aes-stream-internal'),作为私有键挂载到this[token] = { key: ..., iv: ... } - 所有核心方法(如
encryptChunk)开头校验this[token]是否存在且类型正确,缺失即视为非法实例 - 该 token 无法通过
for...in、Object.keys()或 JSON 序列化获取,比字符串键更隐蔽
不推荐但需知晓的误区
不要把 new.target 当作防篡改盾牌:它不能阻止 Reflect.construct(AESStream, args, EvilClass),也不能防御内存 dump 或 DevTools 注入。它的作用是让“非预期调用路径”立刻失败,而非让“预期路径绝对不可破”。真正的防护重心应放在——实例一旦创建,其内部状态是否对外不可见、不可推导、不可复用。

















