npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

warden-for-js

v3.0.2

Published

Warden 是一个面向 Webpack 5 的浏览器端安全加固插件。目前主要用于浏览器扩展构建,通过三层互补机制收敛第三方模块可获得的能力:

Readme

Warden for JS

Warden 是一个面向 Webpack 5 的浏览器端安全加固插件。目前主要用于浏览器扩展构建,通过三层互补机制收敛第三方模块可获得的能力:

| 机制 | 保护目标 | 默认状态 | | --- | --- | --- | | 全局变量 Proxy 沙箱 | 限制第三方模块通过 globalThiswindowself__webpack_require__.g 访问浏览器全局能力 | 开启 | | Webpack require 保护 | 限制模块工厂只能加载依赖图或人工策略明确授权的模块 | 开启 | | 关键原型冻结 | 防止敏感运行时依赖的关键内建对象和方法在业务启动后被替换 | 开启 |

Warden 属于现有 Webpack 运行时上的能力加固,不是独立 Realm,也不提供完整的 SES 式 JavaScript 隔离。

快速接入

const { WardenPlugin } = require("warden-for-js");

module.exports = {
  // ...
  plugins: [
    new WardenPlugin({
      dev: false,
    }),
  ],
};

可用选项:

export type WardenPluginOptions = {
  dev?: boolean;
  policyOverPath?: string;
  policyOutputPath?: string;
  freezePrototypes?: boolean;
  prototypeFreezeMode?: "compat" | "strict";
  protectWebpackRequire?: boolean;
  forceCheck?: boolean;
};

| 选项 | 默认值 | 说明 | | --- | --- | --- | | dev | true | 全局沙箱的开发模式。未声明的全局访问会被放行并记录到 globalThis.__dbg_ru1n;Webpack require 保护仍然严格阻断未授权导入 | | policyOverPath | SecSDK/policy-over.json | 人工策略文件,相对于 Webpack context | | policyOutputPath | SecSDK/policy.json | 构建生成的最终可读策略文件,相对于 Webpack context | | freezePrototypes | true | 是否生成并加载关键原型冻结 bootstrap | | prototypeFreezeMode | "compat" | 原型保护模式;compat 锁定既有成员并保留必要扩展点,strict 额外冻结 Object.prototype 并移除 Function.prototype.toString 兼容例外 | | protectWebpackRequire | true | 是否保护模块工厂收到的 __webpack_require__ | | forceCheck | !dev | 构建检查发现敏感全局权限或三方模块越界依赖时是否终止构建;生产模式默认终止,显式关闭时逐条 console.warn |

freezePrototypesprotectWebpackRequire 相互独立。关闭原型冻结不会关闭 require 保护,反之亦然。

一、全局变量 Proxy 沙箱

Warden 会分析并改写 node_modules 中实际参与打包的 JavaScript 模块。启用沙箱的模块会以类似下面的结构运行:

const g = __webpack_require__.g;

((globalThis, window, self) => {
  // 原模块工厂代码
})(g, g, g);

构建无法解析任何可用源码表示时会直接失败,不再静默生成空策略;多个可解析源码表示的访问结果取并集,避免 loader 前后表示差异漏掉能力。为控制运行时开销,静态可确定的裸全局标识符保持原生词法访问,不会统一改写成 globalThis[...];Proxy 保护仍针对全局对象别名及动态属性路径。

因此模块内通过 globalThiswindowself 或 Webpack 的全局对象 helper 发起的操作都会经过同一个 Proxy。globalThis.windowglobalThis.selfglobalThis.globalThis 也会继续返回当前模块的沙箱,而不是泄露真实全局对象。

沙箱工厂由 Warden 的 Webpack module-execution interceptor 私有持有。interceptor 根据当前 runtime 和 module ID 选择策略、创建沙箱,再通过模块私有且只读的 __webpack_require__.g 交给模块;页面全局和 Worker 全局均不会安装 __mkGb,模块也不能传入任意规则或其他 policy ID 自行创建沙箱。

权限模型

每个模块拥有独立的全局变量规则表:

  • r:允许读取、存在性检查和属性描述符查询。
  • rw:包含读取权限,并允许赋值、删除和 defineProperty
  • 未声明属性:生产模式读取返回 undefined,写入被拒绝;开发模式放行并记录实际访问。
  • 标准 ECMAScript 内建对象的读取默认放行,eval 除外。
  • 已知浏览器全局函数会绑定到真实全局对象,以保持 setTimeoutfetchaddEventListener 等宿主 API 的调用兼容性。

沙箱实现了以下 Proxy 陷阱:

get / set / has / deleteProperty / defineProperty
getOwnPropertyDescriptor / ownKeys
getPrototypeOf / setPrototypeOf
isExtensible / preventExtensions

反射操作与普通属性访问使用同一套权限。Proxy handler 会冻结;传入的规则首先复制到 null-prototype 对象后再冻结,因此污染 Object.prototype 不能伪造模块权限。沙箱以真实 globalThis 为 target,但不会冻结这个 Proxy 或真实全局对象。

开发模式与生产模式

dev: true 用于发现缺失的全局权限:未声明访问会继续执行,并按模块记录到 globalThis.__dbg_ru1ndev: false 才会执行严格的全局读写限制。

这个开关不影响 Webpack require 保护。无论开发或生产模式,未授权模块导入都会立即抛错。

二、Webpack __webpack_require__ 能力保护

开启 protectWebpackRequire 后,Warden 会为每个 Webpack runtime 安装模块执行 interceptor。模块工厂不会收到原始 require,而会收到只属于当前模块的受保护 Proxy。

构建阶段会根据最终 ModuleGraphChunkGraph 和模块 ID 生成授权关系:

  • 同步依赖、动态 import、context、weak context、循环依赖等均从 Webpack 的实际连接中解析。
  • inactive 连接不会进入授权表;TRANSITIVE_ONLY 连接会递归展开。
  • 受全局 Proxy 保护的第三方模块会退出 scope-hoisting,保证一个模块工厂只绑定一份全局策略;其余 concatenated module 的最终工厂仍是 require 授权单元,权限为全部成员依赖的并集。
  • 动态 chunk 后续注册的模块仍经过相同 interceptor。
  • 应用模块、npm 模块、ContextModule、ExternalModule 等实际发射的 JavaScript 工厂都会被保护。

受保护 require 只允许加载授权模块,并只暴露当前模块代码生成阶段真正需要的 runtime helper:

  • .g 是模块私有的虚拟属性,不再改写共享 Webpack runtime 的全局对象。
  • .m.c 只有在模块代码生成明确要求对应 capability 时才可见;工厂项只返回不可调用的占位包装,缓存项只返回不可替换 exports 的冻结快照,不暴露原始工厂或 module record。
  • .t 等可能间接加载模块的 helper 会再次校验目标 ID。
  • .i、未知 helper、constructorprototype__proto__、构造调用以及属性写入、删除和 defineProperty 都会被拒绝。
  • 其余函数型 helper 通过冻结包装调用,对象型 helper 通过递归只读视图暴露;callapplybind 仍可使用,但调用的仍是受保护 require。

拒绝时抛出的错误带有固定错误码:

error.code === "WARDEN_WEBPACK_REQUIRE_DENIED";

错误同时包含调用方 ID、目标 ID 和可解析的模块路径;开发模式还会附带当前允许列表。

人工追加 require 权限

自动依赖以外的精确授权写入 policy-over.json

{
  "src/example.ts": {
    "webpackRequire": {
      "allow": ["node_modules/example/index.js"]
    }
  }
}

路径必须是相对于项目根目录的 POSIX 精确路径,不支持 glob。调用方或目标不存在、目标存在歧义,或者两者不属于同一 runtime 时,构建会失败。

顶层的 enable: false 只关闭该模块的全局 Proxy 沙箱,不会关闭 require 保护。require 保护只能通过插件级 protectWebpackRequire: false 全局关闭。

三、关键原型冻结

开启 freezePrototypes 后,Warden 会发射同源的 warden-prototype-freeze.js,并保证它先于策略 runtime 和业务脚本执行。

当前冻结范围包括:

  • BooleanNumberBigIntStringSymbol
  • ArrayRegExpPromise.prototypeMapSetWeakMapWeakSet
  • Error.prototypeAggregateErrorSuppressedError 及全部原生错误子类;compat 模式保留 V8 调试钩子 Error.stackTraceLimitError.prepareStackTrace 可修改。
  • DisposableStackAsyncDisposableStack(环境存在时)。
  • WeakRefFinalizationRegistryIterator(环境存在时)。
  • ArrayBufferSharedArrayBufferDataView 和全部具体 typed array 构造器及其原型。
  • AtomicsIntl 命名空间,以及环境提供的全部 Intl 构造器及其原型。
  • 环境存在时的 CryptoCryptoKeySubtleCryptoTextEncoderTextDecoder
  • ProxyJSONMath

保护清单集中定义在 src/prototype-protection.ts。测试会校验沙箱默认放行的每个 ECMAScript 全局都在清单中被明确分类,新增全局但遗漏保护策略时会导致测试失败。

compatstrict 模式都会冻结 crypto.subtle 单例及其实际原型,阻止对 digestencrypt 等 Web Crypto 方法进行实例遮蔽、重定义或删除。

同时,默认的 compat 模式为兼容现有生态保留以下扩展点:

  • ObjectObject.prototypeFunctionFunction.prototypeDateDate.prototype 不会被整体冻结,但它们启动时已经存在的成员会被锁定;仍允许增加新成员,也允许派生对象创建同名自有属性。
  • Function.prototype.toString 是既有成员中的明确例外,保留监控 SDK 改写并恢复它的能力。
  • Reflect 对象,允许 reflect-metadata 添加新 API。
  • Promise 构造器,兼容 Dexie 等库临时替换并恢复 Promise 静态方法;Promise.prototype 仍冻结。
  • typed array 共享构造器和共享原型保持可扩展,但其既有静态方法和原型方法会被锁定;Buffer 等子类仍可定义自己的 fromtoStringincludesslice

锁定方式允许派生函数或实例创建同名自有属性,避免破坏 fn.call = valueobj.toString = value 或 Buffer 子类覆盖 inherited typed-array 方法一类代码。

prototypeFreezeMode: "strict" 会进一步深度冻结 ObjectFunctionDatePromise 构造器及其原型、共享 TypedArray 祖先、迭代器/生成器/异步生成器等隐藏 intrinsic 原型,以及原型方法函数和 Array.prototype[Symbol.unscopables] 一类嵌套对象。compat 模式仍保留前述生态兼容扩展点。

直接修改已锁定目标会抛出明确错误,例如:

TypeError: [Warden] Modification of locked target "Reflect.get" is not allowed

可选 API 不存在时会跳过;对已经存在的目标,如果锁定或结果校验失败,bootstrap 会停止业务启动并报告具体目标。

运行时资产与加载顺序

| 运行环境 | 加载方式 | | --- | --- | | 普通 HTML / MV2 background page | 在所有 HTML 的 <head> 首位同步插入 warden-prototype-freeze.js(若开启)、warden-policy-runtime.js、业务脚本 | | MV3 service worker | 从 manifest.json 识别 background.service_worker,在业务代码前执行冻结 bootstrap,再 importScripts("warden-policy-runtime.js") | | manifest content script(默认隔离世界) | 策略封装在 runtime 闭包中,并在脚本首行执行原型冻结 bootstrap | | world: "MAIN" content script / pageProvider.js | 策略封装在各自 runtime 闭包中,不向网页 MAIN world 注入原型冻结脚本 |

所有 HTML 注入均使用外部同源脚本,不生成内联代码,适配浏览器扩展 CSP。嵌套 HTML 会根据 publicPath 或相对目录生成正确引用。

外部页面和 Worker 共用的策略数据保存在 warden-policy-runtime.js。该文件使用路径分段 Trie、去重的依赖集合和紧凑 ID 表减少重复数据,并在入口模块执行前完成结构校验和冻结;它只提供不可变策略记录和诊断,不提供创建沙箱的能力。真正的沙箱工厂位于各 Webpack runtime 的闭包中。content script 与 pageProvider.js 则把策略记录内联到各自的 runtime 闭包,避免依赖页面 MAIN world 的共享策略。

外部 runtime 会提供只读诊断对象:

__wardenDiagnostics.resolvePolicyPath(policyId);
__wardenDiagnostics.resolveWebpackModulePath(moduleId);

诊断对象、只读策略解析器和内部策略记录均不可写、不可配置,并在创建后冻结。策略解析器返回的只是不可变元数据;__mkGb 不再存在于全局对象上。

Policy 文件

构建产物 policy.json

policy.json 是便于审计的最终有效策略,而不是运行时存储格式:

{
  "node_modules/example/index.js": {
    "enable": true,
    "require": [
      "node_modules/example/dependency.js"
    ],
    "global": {
      "chrome": "r",
      "navigator": "rw"
    }
  },
  "node_modules/legacy/index.js": {
    "enable": false
  }
}
  • enable 表示该模块是否启用全局 Proxy 沙箱。
  • require 是模块运行时最终允许加载的模块路径,包括自动依赖与人工追加权限。
  • global 是模块最终允许访问的全局变量及权限。
  • 空的 requireglobal 会被省略。
  • 如果一个模块最终只有 "enable": true,整个模块条目会被省略;"enable": false 始终保留。

动态属性名无法完整表示为静态 global 字段,但运行时通过 Proxy 发起的动态访问仍会按实际属性名校验。构建不会为这类访问打印 warning。

三方模块依赖边界

Warden 会检查最终 policy.json 中的 require 权限:如果来源模块路径以 node_modules/ 开头,而它允许加载的目标模块路径既不以 node_modules/ 开头,也不以 webpack: 开头,且目标文件名不是 package.json,则视为三方模块引用一方代码。webpack: 表示 Webpack 生成或忽略的内部模块,package.json 只提供包元数据,二者都属于安全目标。自动依赖和 policy-over.json 人工追加的 require 权限使用同一检查口径。

开发模式默认逐条输出 console.warn;生产模式(dev: false)默认汇总全部越界依赖并终止构建。可通过显式 forceCheck 覆盖默认值。该检查依赖 protectWebpackRequire 生成最终 require 策略;关闭 require 保护后,policy.json 不包含相应依赖关系,因此不会执行这项边界判断。

敏感全局检查 check.json

如果 Webpack context 下存在 SecSDK/check.json,Warden 会在构建阶段检查最终 policy.json 中的全局权限。配置的键是敏感全局名,值是允许获得该权限的模块路径正则数组:

{
  "document": [
    "^node_modules/react-dom",
    "^node_modules/antd"
  ],
  "chrome": [
    "^node_modules/webextension-polyfill"
  ],
  "fetch": []
}

模块路径与 policy.json 的键一致,使用相对于 Webpack context 的 POSIX 路径。一个模块只要命中对应全局名下的任一 JavaScript 正则表达式即可通过;空数组表示不允许任何模块获得该全局权限。rrw 使用同一套模块白名单,人工 policy-over.json 追加的权限也会参与检查。未在 check.json 中声明的全局名不受这项构建检查影响。

开发模式默认对每个违规的“模块 + 全局名”输出一条 console.warn;生产模式(dev: false)默认汇总全部违规并终止构建。可显式设置 forceCheck 覆盖默认行为:

new WardenPlugin({
  forceCheck: true,
});

check.json 不存在时会跳过检测;文件存在但 JSON 结构、数组项或正则表达式无效时始终终止构建。

人工策略 policy-over.json

{
  "node_modules/example/index.js": {
    "enable": true,
    "global": {
      "allow": {
        "chrome": "r",
        "navigator": "rw"
      }
    },
    "webpackRequire": {
      "allow": [
        "node_modules/example/dependency.js"
      ]
    }
  }
}

人工 global.allow 会覆盖同名自动检测权限,webpackRequire.allow 会追加到依赖图授权。所有模块路径都使用相对于 Webpack context 的 POSIX 路径。

安全边界

使用 Warden 时需要注意以下边界:

  • 全局 Proxy 沙箱目前只改写 node_modules 模块;应用源码不会自动进入该层沙箱,但仍受 Webpack require 保护。
  • 静态可确定的裸全局标识符按设计保持原生词法访问,不经过 Proxy;构建期分析与 check.json 负责审计这类能力。动态生成代码、未被解析器看到的代码和提前保存的真实引用同样不在 Proxy 保证内。
  • Symbol 属性当前默认允许读写,以保持宿主对象和运行时协议兼容。
  • Webpack runtime、RuntimeModule 和 chunk loader 是受信任基础;保护对象是传给业务模块工厂的 require。
  • 未进入全局 Proxy 沙箱的 concatenated factory 内部源码模块共享一个 require 授权单元,Warden 不隔离同一工厂内部的直接符号访问;受全局 Proxy 保护的第三方模块会主动退出 concatenation。
  • 默认 prototypeFreezeMode: "compat"Object.prototype 仍可扩展,因此不等于消除全部普通对象原型污染路径;需要阻断这一路径时使用 "strict"
  • Warden 不冻结应用内部的 Keyring、Buffer 或其他业务类,也不替代私钥材料的对象级生命周期管理。
  • manifest 默认隔离世界的 content script 会执行关键原型冻结;显式 world: "MAIN" 的 content script 和 pageProvider.js 不冻结网页原型,避免改变被访问页面的 JavaScript 行为。
  • 能进行动态代码生成或已提前获得真实对象引用的代码,不应被当作完全不可信的任意 JavaScript 放入当前沙箱;需要这种隔离时应使用独立 Realm、Worker 或进程边界。

源码架构与维护

包根入口保持精简,Webpack 编译期逻辑与生成到浏览器中的运行时代码分开维护:

| 模块 | 职责 | | --- | --- | | src/index.ts | 公共入口,只导出 WardenPluginWardenPluginOptions | | src/plugin/warden-plugin.ts | 初始化各协作模块并注册 Webpack 生命周期 hook | | src/plugin/module-policy.ts | 识别需要沙箱化的模块、选择分析源码、合并全局权限并改写模块工厂 | | src/plugin/webpack-module-graph.ts | 解析模块路径、active dependency、runtime requirement 和 concatenated module | | src/plugin/webpack-policy.ts | 生成每个 runtime 的 require/global 策略,处理人工授权并汇总审计记录 | | src/plugin/policy-files.ts | 读取人工策略和检查规则,生成、校验并稳定写入 policy.json | | src/plugin/runtime-assets.ts | 发射冻结/策略资产,并处理 HTML、MV2、MV3 的加载顺序 | | src/plugin/contracts.ts | 插件选项、编译期状态和内部策略结构等共享契约 | | src/globals.tssrc/global/ | 保留原有深层入口,同时分别维护能力清单、AST 分析、bind 改写和 Proxy 沙箱 |

一次编译的主要数据流是:

模块源码分析
    ↓
全局权限与 Webpack 依赖策略生成
    ↓
人工策略合并、敏感权限与依赖边界审计
    ↓
policy.json 写入、runtime 资产发射与页面/Worker 注入

维护生成代码时需要遵守以下约束:

  • mkCompartmentFactory 会通过 toString() 写入 Webpack runtime,函数体必须自包含,不能依赖模块闭包中的常量或辅助函数。
  • 策略中的模块路径统一为相对于 Webpack context 的 POSIX 路径;生成模块使用稳定的 webpack: 标识。
  • 不要随意调整 Webpack hook 或 processAssets stage。模块分析、runtime policy 生成、资产发射和最终注入依赖现有先后顺序。
  • src/index.tssrc/globals.ts 是兼容入口。内部实现可以继续拆分,但包根导出及已有深层模块导出应保持稳定。

开发与验证

npm run check
npm test

npm run check 只执行 TypeScript 类型检查;npm test 会先构建 dist/,再运行全部 Node.js 测试。

测试覆盖全局 Proxy 反射陷阱、关键原型兼容性、策略 runtime 校验、Webpack 同步与异步依赖、context、concatenation、HMR、require 旁路以及 HTML/MV2/MV3 资产加载顺序。test/exports.test.js 额外锁定包根和已有深层模块的运行时导出,防止内部模块化意外变成破坏性 API 变更。