• 请不要在回答技术问题时复制粘贴 AI 生成的内容
aaa69532
V2EX  ›  程序员

geek-csp-sandbox-eval

  •  
  •   aaa69532 · 1h 52m ago · 106 views

    Running LLM-Generated Code in a Chrome Extension: An MV3 CSP Adventure

    Chrome Manifest V3 has one commandment above all others: thou shalt not eval. No new Function, no dynamic code strings, anywhere in an extension's privileged contexts. The policy killed a generation of extensions and made MV3 migration famously painful.

    Now consider Scrapewright's core requirement: it must execute scraping code written by an LLM at runtime — arbitrary, generated-after-install, string-shaped JavaScript — inside a Chrome extension. On its face, that's the exact thing MV3 exists to prevent. The repo is a working solution to this puzzle, and the path it takes says a lot about Chrome's actual security model (as opposed to the documented one).

    The map of where code may run

    An MV3 extension has, broadly, these execution contexts, each with its own CSP:

    Context eval/new Function? Privileges
    Service worker (background.js) No Full chrome.* API
    Extension pages (popup, options) No Full chrome.* API
    Content scripts (in the page) No DOM access + limited messaging
    Declared sandbox page Yes None — isolated origin

    The escape hatch is the last row: pages listed under the manifest's sandbox key are loaded in a sandboxed iframe with a unique opaque origin — no chrome.* access, no same-origin relationship with anything, DOM-only. In exchange for that quarantine, Chrome relaxes their CSP to allow eval and new Function. It's the sanctioned container for "code I don't trust": plugins, math-expression evaluators, REPLs.

    That's the doctrine. The practice is where it gets interesting.

    The relay chain

    Scrapewright's LLM-generated step scripts can't just sit in a sandbox and compute — they need to touch the DOM of an arbitrary, already-open tab (click, extract, hover, scroll). The sandbox can't do that directly (no privileges), and the tab's own page CSP forbids injecting evaluators into it (many sites also CSP away unsafe-eval, and injecting code into the page means living inside the page's threat model anyway).

    So a request relay chain bridges the two sides:

    LLM step script (sandbox iframe)
       │  "$extract('.item .price')"
       ▼  postMessage → DOM_REQUEST
    Offscreen document (offscreen.js — owns the sandbox iframe)
       ▼  chrome.runtime message
    Service worker (background.js — has chrome.tabs)
       ▼  chrome.tabs.sendMessage
    Content script in the target tab (content-script.js)
       ▼  performs the DOM op
       └── response flows back the same way → sandbox resolves the promise
    

    Design choices worth stealing:

    • Why an offscreen document at all? MV3 service workers can't own iframes; DOM-holding contexts need a document. chrome.offscreen gives the extension a hidden page that survives SW suspensions — a stable host for the sandbox iframe and for a tabIdStack that maps concurrent-looking DOM requests back to their originating tabs.
    • Why not inject generated code into the target page? Because then your code runs under the page's CSP and the page's mutation surface. In the sandbox, generated code is a prisoner that can only ask guards (via message passing) to act on the world. LLM hallucination or malicious page content can't escalate through that boundary.
    • Response dedup and timeouts. Each DOM_REQUEST carries an id; responses are deduped across the relay (a content-script reply can arrive both directly and via a background rebroadcast), and a timeout path tears the stack down so a hung tab can't leak stack slots. Reading that code is a lesson in defensive async plumbing.

    The philosophical bit

    What this architecture really demonstrates is that MV3's CSP isn't a prohibition on dynamic code — it's a routing requirement: dynamic code must live in a context that has no authority, so that capability flows only through explicit, auditable message channels. The sandbox prisoner never handles a capability it shouldn't; every privileged act (chrome.tabs, chrome.debugger, storage) stays in code that was static at review time and is covered by the manifest's declared permissions.

    For an LLM-driven system that's more than hygiene — it's the containment story. Generated code is expected to be occasionally wrong (that's the medium). Wrong code in the sandbox fails a DOM request. Wrong code with chrome.debugger access would be a different class of incident. The architecture assumes its own generator is fallible, and the blast radius is the point.

    Go read it

    The chain is small enough to hold in your head in an afternoon — sandbox.js (the evaluator), offscreen.js + lib/offscreen-executor.js (the host/relay), background.js (the bridge), content-script.js (the executor in the page) — and the repo's whitepaper walks it module by module. If you're building anything MV3 that touches user- or model-authored code, this is prior art worth an hour: github.com/singhand-labs/scrapewright (GPLv3, plain JS, no build step between you and the code).

    No Comments Yet
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2835 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 14:09 · PVG 22:09 · LAX 07:09 · JFK 10:09
    ♥ Do have faith in what you're doing.