Object.seal仅密封对象自身属性(禁增删重配置),不限制变量重新赋值;防覆盖关键在引用控制+作用域封装+冻结时机,需结合只读导出或冻结副本实现真正防护。

Object.seal 本身不能防止对象被整体覆盖,它只约束对象自身的属性操作(增、删、重配置),对变量引用层面的赋值(如 config = newConfig)完全无感。所谓“防止被作为普通对象覆盖”,关键不在 seal,而在引用控制 + 作用域封装 + 冻结时机。
真正起效的组合是:密封结构 + 只读导出 + 模块级不可变封装,让攻击者连拿到可写引用的机会都没有。
✅ 密封只是结构防护,不是引用防护
Object.seal(config) 的实际效果是:
-
config.newKey = 'x'→ 报错(禁止新增) -
delete config.timeout→ 报错(禁止删除) -
config.timeout = 10000→ ✅ 允许(值仍可改) -
config = { timeout: 20000 }→ ✅ 完全合法(变量被重新赋值)
也就是说:seal 锁的是“对象内部”,不是“变量指向”。
✅ 防止覆盖的核心手段:切断可写引用链
你需要让业务代码根本拿不到原始对象的可变引用,只提供受控访问:
// config.js —— 模块内私有原始配置
const rawConfig = {
cipher: 'AES-256-GCM',
ivLength: 12,
tagLength: 16,
streaming: true,
fallbackMode: 'reject'
};
Object.seal(rawConfig); // 锁定结构:防字段误增/删/重定义
// ❌ 不导出 rawConfig
// ✅ 只导出安全访问方式
export const getConfig = (key) => {
if (!(key in rawConfig)) throw new Error(`Invalid config key: ${key}`);
return rawConfig[key];
};
// 或导出冻结副本(更彻底,兼顾值不可变)
export const CONFIG = Object.freeze({ ...rawConfig });这样调用方只能读,无法赋值覆盖,也无法意外修改嵌套结构(因副本已冻结)。
✅ 加密流式配置需额外注意的点
这类配置常含敏感字段(如算法名、密钥长度、模式标识),容易被插件或调试脚本篡改:
-
cipher字段若被改成'none'或'AES-128-ECB',会直接降级安全等级 -
streaming: false被设为true可能引发内存溢出 -
fallbackMode若从'reject'改成'ignore',会掩盖解密失败
因此建议:
- 在
seal基础上,对关键字段做运行时校验(如getConfig('cipher')返回前检查是否在白名单内) - 对
streaming、fallbackMode等布尔/枚举型字段,用Object.defineProperty显式设为writable: false(seal不动writable,需手动加固) - 若配置含嵌套对象(如
{ crypto: { padding: 'pkcs7' } }),必须递归seal或改用Object.freeze(seal不处理嵌套)
✅ 为什么不用 Object.freeze?
因为加密配置有时需要运行时微调(例如灰度阶段临时启用 debugMode: true),而 freeze 连值都不让改。seal 正好平衡:
- ✅ 结构稳定(字段名不会被错写、注入或删掉)
- ✅ 值可更新(开关类字段仍支持热切换)
- ✅ 调试友好(开发者可临时改值验证逻辑,不破坏结构)
只要配合只读导出,就既保安全,又留弹性。

















