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 🙏

© 2024 – Pkg Stats / Ryan Hefner

mip-sandbox

v1.1.13

Published

sandbox tools for MIP project

Downloads

26

Readme

mip-sandbox

mip-sandbox 是一系列跟 MIP 沙盒相关的工具,包括沙盒对象定义、代码沙盒检测工具、代码沙盒替换工具等等。

安装

通过 npm 进行安装:

npm install --save-dev mip-sandbox

使用

沙盒对象

var sandbox = require('mip-sandbox')

// setTimeout
sandbox.setTimeout(function () {}, 3000)

// document
sandbox.document.cookie

// ...

不安全全局变量检测

使用 mip-sandbox/lib/unsafe-detect 方法进行不安全全局变量检测,该函数的定义如下

/**
 * 不安全全局变量检测
 *
 * @params {string|AST} code 代码字符串或代码 AST
 * @params {Array=} keywords 安全全局变量声明列表,
 *                           在默认情况下,所有全局变量包括 window document 等均认为不安全,
 *                           需要传入该参数进行条件过滤
 * @return {Array.<ASTNode>} 不安全全局变量列表
 */

使用例子如下:

var detect = require('mip-sandbox/lib/unsafe-detect')
var keywords = require('mip-sandbox/lib/keywords')

var code = `
var a = 1
console.log(b)
`

// 严格模式 请使用 keywords.WHITELIST_STRICT
// 在前端使用时,可通过 MIP.sandbox.WHITELIST 去拿该列表
var results = detect(code, keywords.WHITELIST)

console.log(result)

// [
//   {
//      type: 'Identifier',
//      name: 'b',
//      range: [...]
//      loc: [...]
//   }
// ]

不安全全局变量替换

使用 mip-sandbox/lib/generate 方法进行不安全全局变量替换,该函数的定义如下

/**
 * 不安全全局变量替换
 *
 * @param {string|AST} code 代码字符串或代码 AST
 * @param {Array=} keywords 安全全局变量声明列表,
 *                           在默认情况下,所有全局变量包括 window document 等均认为不安全,
 *                           需要传入该参数进行条件过滤
 * @param {Object=} options options
 * @param {string=} options.prefix 默认全局变量注入的前缀,默认为 MIP.sandbox
 * @param {Object=} options.escodegen 透传给 escodegen 的参数
 * @return {string} 替换后的代码字符串
 */

使用例子如下:

var generate = require('mip-sandbox/lib/generate')
var keywords = require('mip-sandbox/lib/keywords')

var code = `
var a = 1
console.log(b)
window.console.log(a)
`

var result = generate(code, keywords.WHITELIST_RESERVED)

console.log(result)

// var a = 1
// console.log(MIP.sandbox.b)
// MIP.sandbox.window.console.log(a)

对于严格模式下 window 需要替换成 MIP.sandbox.strict.window 在这种情况下,需要传入第三个参数:

options.prefix

默认的 options.prefix === 'MIP.sandbox',在严格下可以传入 MIP.sandbox.strict,得到的结果将如下所示:

var result = generate(code, keywords.WHITELIST_STRICT_RESERVED, {prefix: 'MIP.sandbox.strict'})

// var a = 1
// console.log(MIP.sandbox.b)
// MIP.sandbox.strict.window.console.log(a)

该方法使用 escodegen 实现的 ast to string

该方法的第三个参数 options.escodegen 将会透传给 escodegen 因此比如需要返回 sourcemap 的话,请于第二个参数传入 sourcemap 相关参数

如:

var output = generate(code, keywords.WHITELIST_RESERVED, {
  escodegen: {
    sourceMap: 'name',
    sourceMapWithCode: true
  }
})

// output.code
// output.map

对于不需要生成 sourceMap 的情况,可以使用 generate-lite 来去掉 source-map 相关代码以减小打包体积。

该方法的定义如下:

/**
 * 不安全全局变量替换
 *
 * @param {string|AST} code 代码字符串或代码 AST
 * @param {Array=} keywords 安全全局变量声明列表,
 *                           在默认情况下,所有全局变量包括 window document 等均认为不安全,
 *                           需要传入该参数进行条件过滤
 * @return {string} 替换后的代码字符串
 */
var generate = require('mip-sandbox/lib/generate-lite')
var keywords = require('mip-sandbox/lib/keywords')
var code = generate(code, keywords.WHITELIST_RESERVED)

沙盒检测替换优化

在某些场景下需要同时使用 detect 和 generate 去实现功能,这时,如果对这两个方法传入的 code 都是字符串的话,就需要对字符串做两次 ast 解析和标记,为了解决这个问题,可以调用 global-mark 生成解析标记好的 ast,再将 ast 传入 detect 和 generate 中,从而提高效率:

var mark = require('mip-sandbox/lib/global-mark')
var detect = require('mip-sandbox/lib/unsafe-detect')
var generate = require('mip-sandbox/lib/generate')
var keywords = require('mip-sandbox/lib/keywords')

var ast = mark(code)
var unsafeList = detect(ast, keywords.WHITELIST)
var generated = generated(ast, keywords.WHITELIST_RESERVED)

沙盒替换规则

沙盒替换采用白名单机制,即只允许开发者使用部分全局 API,不在白名单里的方法会在编译时自动将相关代码加上 MIP.sandbox 前缀,这样就会导致报错。

比如下面一段代码:

const a = require('path/to/a')
console.log(a)
eval('console.log("b")')
window.console.log(b)

setTimeout(function () {
  console.log(this)
}.bind(undefined), 3000)

将会替换成:

const a = require('path/to/a')
console.log(a)
MIP.sandbox.eval('console.log("b")')
MIP.sandbox.window.console.log(MIP.sandbox.b)

setTimeout(function () {
  console.log(MIP.sandbox.this(this))
}.bind(undefined), 3000)

解释

上述代码中 require、console、eval、window、setTimeout、undefined、b 属于全局变量,其中 require、console 属于安全全局变量,所以不做任何处理;eval、window、b 属于不安全全局变量,因此会加上 MIP.sandbox 前缀,其中 MIP.sandbox.window 是有定义的,而 MIP.sandbox.eval 和 MIP.sandbox.b 没有定义,因此上述代码会报错。

this 沙盒替换

上述代码中经过对比可以看到 this 被替换成了 MIP.sandbox.this(this),这是因为在类似 function () {}.bind(undefined) 的情况下,函数内的 this 指向 window,而 诸如 document.addEventListener('scroll', function () {}),的回调里的 this 指向 document,这些都是不安全全局变量,因此需要 MIP.sandbox.this() 方法将 window 和 document 替换掉:

MIP.sandbox.this = function (that) {
  return that === window ? MIP.sandbox : that === document ? MIP.sandbox.document : that
}

严格模式

在 mip-script 中,理论上只允许进行数据运算和发请求等等操作,不允许直接操作 DOM ,因此在 mip-script 中写的 js 将会以沙盒的严格模式进行全局变量替换,比如 window 会被替换成 MIP.sandbox.strict.window、 this 将会替换成 MIP.sandbox.strict.this(this)

其中 MIP.sandbox.strict 是 MIP.sandbox 的子集。

可用全局变量

以下变量是 MIP sandbox 暴露给用户可直接使用的全局变量,后续会根据实际需要进行增加或减少。

原生安全的全局变量

以下全局变量被认为是安全的,在沙盒注入过程中不会加上任何沙盒前缀。

var ORIGINAL = [
  'Array',
  'ArrayBuffer',
  'Blob',
  'Boolean',
  'DOMError',
  'DOMException',
  // https://github.com/mipengine/mip2/issues/336
  'DataView',
  'Date',
  'Error',
  'Float32Array',
  'Float64Array',
  'FormData',
  'Headers',
  'Infinity',
  'Int16Array',
  'Int32Array',
  'Int8Array',
  'JSON',
  'Map',
  'Math',
  'NaN',
  'Number',
  'Object',
  'Promise',
  'Proxy',
  'ReadableStream',
  'ReferenceError',
  'Reflect',
  'RegExp',
  'Request',
  'Response',
  'Set',
  'String',
  'Symbol',
  'SyntaxError',
  // https://github.com/mipengine/mip2/issues/347
  'TextDecoder',
  'TextEncoder',
  'TypeError',
  'URIError',
  'URL',
  'URLSearchParams',
  'Uint16Array',
  'Uint32Array',
  'Uint8Array',
  'Uint8ClampedArray',
  // 1.0.17 新增 WebSocket
  'WebSocket',
  'WritableStream',
  // issue https://github.com/mipengine/mip2/issues/62
  'crypto',
  'console',
  'decodeURI',
  'decodeURIComponent',
  'localStorage',
  'navigator',
  'sessionStorage',
  'screen',
  'undefined',
  'devicePixelRatio',
  'innerHeight',
  'innerWidth',
  'isSecureContext',
  'length',
  'outerHeight',
  'outerWidth',
  'screenLeft',
  'screenTop',
  'screenX',
  'screenY',
  'scrollX',
  'scrollY',
  // mip-data ready status
  'mipDataPromises',
  // https://github.com/mipengine/mip2/issues/347
  'atob',
  'clearInterval',
  'clearTimeout',
  'encodeURI',
  'encodeURIComponent',
  'escape',
  'fetch',
  'getComputedStyle',
  'isFinite',
  'isNaN',
  'matchMedia',
  'parseFloat',
  'parseInt',
  'setInterval',
  'setTimeout',
  'unescape',
  // mip1 polyfill
  'fetchJsonp'
  ]

var RESERVED = [
  'arguments',
  'require',
  'module',
  'exports',
  'define',
  'import',
  // process.env.NODE_ENV
  'process'
]

普通模式的沙盒安全变量

普通模式下的沙盒安全变量包含原生安全变量的全部,并且添加了以下变量:

// 全局安全变量
var WHITELIST_ORIGINAL = [
  ...ORIGINAL,
  ...RESERVED,
  // https://github.com/mipengine/mip2/issues/143
  'CustomEvent',
  'File',
  'FileList',
  'FileReader',
  'Image',
  'ImageBitmap',
  'MutationObserver',
  'Notification',
  // 待定
  'history',
  // 待定
  'location',
  'scrollbars',
  'addEventListener',
  'cancelAnimationFrame',
  'createImageBitmap',
  'removeEventListener',
  'requestAnimationFrame',
  'scrollBy',
  'scrollTo',
  'scroll',
  'webkitCancelAnimationFrame',
  'webkitRequestAnimationFrame',
]

// 自定义安全变量
var WHITELIST_CUSTOM = [
  {
    name: 'document',
    // document 允许使用以下属性或方法
    properties: [
      // https://github.com/mipengine/mip2/issues/95
      'domain',
      'head',
      'body',
      'title',
      'cookie',
      'referrer',
      'readyState',
      'documentElement',
      'createDocumentFragment',
      // https://github.com/mipengine/mip2/issues/470
      'execCommand',
      'getElementById',
      'getElementsByClassName',
      'getElementsByTagName',
      'querySelector',
      'querySelectorAll',
      // createElement 禁止创建 SCRIPT 标签
      {
        name: 'createElement',
        getter: function () {
          return function (nodename) {
            if (typeof nodename === 'string' && nodename.toLowerCase()) {
              console.error('[MIP] 禁止创建 SCRIPT 标签引入第三方 JS 脚本')
            }
            return document.createElement(nodename)
          }
        }
      }
    ]
  },
  {
    name: 'window',
    // window 指向 window.MIP.sandbox
    getter: 'MIP.sandbox'
  },
  {
    name: 'MIP',
    // MIP 指向 window.MIP
    getter: 'MIP'
  }
]

因此,白名单的定义为,普通模式下的全局安全变量与自定义安全变量的集合:

var WHITELIST = WHITELIST_ORIGINAL.concat(WHITELIST_CUSTOM.map(item => item.name))
var WHITELIST_RESERVED = WHITELIST_ORIGINAL

严格模式下的沙盒安全变量

严格模式下的沙盒安全变量包含原生安全变量的全部,并且添加了以下变量:

// 安全原生全局变量
var WHITELIST_STRICT_ORIGINAL = [
  ...ORIGINAL,
  ...RESERVED
]

// 安全自定义全局变量
var WHITELIST_STRICT_CUSTOM = [
  // 只允许访问 document.cookie
  {
    name: 'document',
    properties: [
      'cookie',
      // https://github.com/mipengine/mip2/issues/95
      'domain'
    ]
  },
  // location 只放开读权限
  {
    name: 'location',
    access: 'readonly',
    properties: [
      'href',
      'protocol',
      'host',
      'hostname',
      'port',
      'pathname',
      'search',
      'hash',
      'origin'
    ]
  },
  // MIP 对象只开放部分工具函数
  {
    name: 'MIP',
    access: 'readonly',
    properties: [
      'watch',
      'setData',
      'viewPort',
      'util',
      'sandbox',
      {
        name: 'viewer',
        access: 'readonly',
        properties: [
          'isIframed',
          'sendMessage',
          'open'
        ]
      },
      // 'viewer',
      'MIP_ROOT_PAGE'
    ]
  },
  // 严格模式下的 window 对象指向 MIP.sandbox.strict
  {
    name: 'window',
    getter: 'MIP.sandbox.strict'
  }
]

因此,严格模式下白名单的定义为,普通模式下的全局安全变量与自定义安全变量的集合:

var WHITELIST_STRICT = WHITELIST_STRICT_ORIGINAL.concat(WHITELIST_STRICT_CUSTOM.map(item => item.name))
var WHITELIST_STRICT_RESERVED = WHITELIST_STRICT_ORIGINAL