"MV3's security model is opinionated to the point of pickiness — and that's the feature, not the bug."
The Extension Sandbox
Every Chrome extension runs in its own renderer process, isolated from the host pages it interacts with. The extension has access to the chrome.* APIs and a private origin (chrome-extension://<id>/), but it can't reach into the host page's JavaScript context directly. Communication happens through deliberate channels — messages, content scripts injected into isolated worlds, and Chrome's tab APIs.
This isolation is the entire reason extensions are safer than user-script injectors. The host page can't trivially read your extension's storage, and your extension can't accidentally pollute the host's global scope.
Content Security Policy in MV3
MV3 ships a strict default CSP for extension pages — popup, options, side panel:
script-src 'self'; object-src 'self';
Translated: scripts run only from the extension package itself. No inline <script> tags, no eval(), no new Function(), no JavaScript URIs in handlers. Lesson 4's CSP gotcha was a direct consequence of this default.
You can tighten the CSP further (rare), but loosening it is heavily restricted. 'unsafe-eval' is banned outright. 'unsafe-inline' is banned for scripts. 'wasm-unsafe-eval' is allowed if you genuinely need WebAssembly compilation.
host_permissions — What Pages You Can Touch
Separate from the API-capability permissions list, host_permissions declares which URLs the extension is allowed to read DOM from or inject scripts into. Two patterns:
- Narrow — list specific origins:
["https://news.ycombinator.com/*", "https://*.github.com/*"]. Chrome shows the user exactly which sites are affected at install. - Broad —
["<all_urls>"]. Triggers a stark install warning: "Read and change all your data on all websites." Use only when the extension genuinely needs page access everywhere (ClipDeck will — selection capture must work on any page).
The middle path is activeTab in the API permissions list — a just-in-time grant: when the user clicks the toolbar icon, Chrome temporarily gives the extension the current tab's URL + DOM access, expiring on tab navigation. No install-time warning, no broad surface.
The Remote Code Ban
MV3 forbids fetching JavaScript over the network and executing it. Concretely:
- No
fetch("https://...")followed byeval()on the response. - No
<script src="https://cdn.example.com/foo.js">in extension HTML pages. - No
new Function(remoteString).
What's allowed: data fetched over the network (JSON, text, images) treated as data. The line is execution. If a server tells your extension what to render, fine. If a server tells your extension what to run, banned.
chrome.permissions API — Asking on Demand
For permissions declared under optional_permissions or optional_host_permissions, the extension can request them at runtime via chrome.permissions.request. Chrome shows a consent prompt; the user accepts or denies. This is the polite alternative to declaring everything upfront and scaring users at install.
For ClipDeck, all permissions will stay required-and-declared — there's no useful optional-permission flow for a single-soul utility on a private tailnet. But knowing the API exists matters when designing fleet-distributable extensions that don't want to scare every Mac on install.
<script> tags or onclick= handlers is the recovery path.