You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何访问沙箱iframe中嵌套iframe的contentWindow.document并控制权限

问题需求与遇到的问题

我需要实现以下嵌套沙箱iframe的需求:

  • 创建沙箱化的<iframe>(frame1):允许运行脚本,但禁止访问父级页面
  • frame1内部创建沙箱化iframe(frame2):禁止运行任何JavaScript,但frame1必须能对其进行DOM操作

已知矛盾:如果给frame1设置sandbox="allow-same-origin",frame1确实能访问frame2,但同时也获得了访问父级页面的权限,不符合需求。

我尝试了多种sandbox属性值与iframe源设置的组合,遇到两个核心错误:

  • 错误1:不允许加载本地资源: blob:....
  • 错误2:未捕获DOMException: 无法从'Window'读取命名属性'document':阻止源为"null"的框架访问跨源框架

测试代码片段

var inner = [0,0,0,0]
var outer = [0,0,0]
var focus = null

document.body.innerHTML = [
  [0,0],[0,1],[0,2],
  [1,0],[1,1],[1,2],
  [2,0],[2,1],[2,2],
  [3,0],[3,1],[3,2],
].map(([i,o]) => `<button>${i}-${o}</button>`).join('')
const container = document.createElement('div')

const buttons = [...document.body.children]
document.body.append(container)
buttons.map(btn => { btn.onclick = reload })
function reload () {
  if (focus) focus.style = ''
  const btn = focus = this
  btn.style = 'background-color: pink;'
  const text = btn.textContent
  const [I,O] = text.split('-').map(Number)
  inner = inner.map((x, i) => i === I)
  outer = outer.map((x, o) => o === O)
  container.replaceChildren()
  spawn(container)
}

// -----------------------------------------------
async function spawn (element) {
  const program = 'test'

  const htmlsrc = `<!DOCTYPE html>
  <html><head><meta charset="utf-8"></head><body>333</body></html>`
  const blobsrc = new Blob([htmlsrc], { type: "text/html" })
  const hrefsrc = URL.createObjectURL(blobsrc)

  const datauri = `data:text/html;charset=utf-8,${htmlsrc}`

  const src_js = `
    const iframe = document.createElement('iframe')
    iframe.setAttribute('sandbox', 'allow-same-origin')
    const html = \`<!DOCTYPE html>
    <html><head><meta charset="utf-8"></head><body>333</body></html>\`
    const blob = new Blob([html], { type: "text/html" })

    const href = URL.createObjectURL(blob)
    const href2 = "${hrefsrc}"
    const href3 = \`${datauri}\`

    console.log({ href: href })
    console.log({ href2: href2 })
    console.log({ href3: href3 })
    console.log({ lhref: location.href })

    // not allowed to load resource
    if (${inner[0]}) iframe.setAttribute('src', href2)

    // no cross origin access:
    if (${inner[1]}) iframe.setAttribute('src', href)
    if (${inner[2]}) iframe.setAttribute('srcdoc', html)
    if (${inner[3]}) iframe.setAttribute('src', href3)

    iframe.onload = () => {
      console.log("readonly iframe loaded")
      const innerDoc = iframe.contentWindow.document
      console.log(innerDoc.body.innerHTML)
    }
    document.body.appendChild(iframe)
  `

  const string = src_js
  const sandbox = 'allow-scripts'

  const html = index_html(wrap(string, program))
  const src = _2href(_2blob(html), `#${program}`)
  const srcuri = `data:text/html;base64,${btoa(html)}`

  if (outer[0]) {
    const { global, data, port } = await iframer(element, { srcdoc:html, sandbox })
    }
  if (outer[1]) {
    const { global, data, port } = await iframer(element, { src: srcuri, sandbox })
    }
  if (outer[2]) {
    const { global, data, port } = await iframer(element, { src, sandbox })
    }
}
// ----------------------------------------------------------------------
function _2blob (html) { return new Blob([html], { type: "text/html" }) }
function _2href (blob, query = '') { return URL.createObjectURL(blob) + query }
function index_html (source) {
  return `<!DOCTYPE html>
  <html>
    <head><meta charset="utf-8"></head>
    <body>${source}</body>
  </html>`
}
function wrap (source_js, program) {
  const filepath = `${program || '(anonymous)'}.js`
  const bootloader_js = `;(async element => {
    // document.currentScript.remove()
    const source = element.textContent
    element.remove()
    console.log('load: ' + '${program}')
    eval(source)
    //# sourceURL=(iojs:bootloader)
    //# ignoreList=(iojs:bootloader)
  })(document.querySelector('[type="text"]'))`
  source_js = `;(async () => {
    console.log('run: ' + "${filepath}");${source_js}
    //# sourceURL=${filepath}
    //# ignoreList=${filepath}
  })()`
  return `
    <script type="text">${source_js}</`+`script>
    <script>${bootloader_js}</`+`script>
  `
}
// ----------------------------------------------------------------------
function iframer (element, { src, srcdoc, sandbox = '', timeout } = {}) {
  const el = document.createElement('div')
  const sh = el.attachShadow({ mode: 'closed' })
  sh.innerHTML = `<iframe sandbox="${sandbox}"></iframe>`
  const [iframe] = sh.children
  const { promise, resolve, reject } = Promise.withResolvers()
  iframe.onload = onload
  window.addEventListener('message', onmessage)
  const id = timeout !== undefined ? setTimeout(ontimeout, timeout) : null
  if (src) iframe.src = src
  else if (srcdoc) iframe.srcdoc = srcdoc
  element.append(el)
  return promise
  function ontimeout () {
    window.removeEventListener('message', onmessage)
    iframe.onload = undefined
    reject(new Error('iframe timeout'))
  }
  function onload () {
    clearTimeout(id)
    iframe.onload = undefined
    window.removeEventListener('message', onmessage)
    resolve({ global: iframe.contentWindow, data: null, port: null })
  }
  function onmessage (event) {
    const { source, data = null, ports: [port = null] } = event
    if (source === iframe.contentWindow) {
      clearTimeout(id)
      iframe.onload = undefined
      window.removeEventListener('message', onmessage)
      const sorigin = sandbox.includes('allow-same-origin')
      resolve({ global: sorigin ? iframe.contentWindow : null, data, port })
    }
  }
}

内容的提问来源于stack exchange,提问作者serapath

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 17:37:36