[{"data":1,"prerenderedAt":346},["ShallowReactive",2],{"post-extension-ceiling-1-mandatory-screen-recording":3,"post-next-extension-ceiling-1-mandatory-screen-recording":201,"post-nav-extension-ceiling-1-mandatory-screen-recording":340},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"heading":10,"date":11,"minutes":12,"series":13,"part":14,"parts":15,"summary":16,"short":17,"body":18,"_type":195,"_id":196,"_source":197,"_file":198,"_stem":199,"_extension":200},"\u002Fblog\u002Fextension-ceiling-1-mandatory-screen-recording","blog",false,"","The extension ceiling, part 1: mandatory screen recording","A buyer asks this question in every enterprise browser evaluation: can we record the screen for high-risk sessions. Not screenshots on a violation, recording, mandatory, for contractors and offshore teams. If your security tooling lives in an extension, the honest answer is no, and the dishonest answers are worth understanding.","Mandatory screen recording","2026-02-17",7,"The extension ceiling",1,3,"If your security tool needs to watch the screen to do its job, you have already left the extension model. The mechanics of why.","Extensions cannot record the screen without per-session user consent. The workarounds are partial reconstructions, and managed policy cannot consent on the user's behalf. If recording is mandatory, the requirement is a browser, not an extension.",{"type":19,"children":20,"toc":188},"root",[21,28,35,57,62,68,73,153,159,172,178,183],{"type":22,"tag":23,"props":24,"children":25},"element","p",{},[26],{"type":27,"value":9},"text",{"type":22,"tag":29,"props":30,"children":32},"h2",{"id":31},"what-the-extension-apis-actually-allow",[33],{"type":27,"value":34},"What the extension APIs actually allow",{"type":22,"tag":23,"props":36,"children":37},{},[38,40,47,49,55],{"type":27,"value":39},"Chrome gives an extension two routes to pixels. ",{"type":22,"tag":41,"props":42,"children":44},"code",{"className":43},[],[45],{"type":27,"value":46},"chrome.desktopCapture",{"type":27,"value":48}," shows the user a picker and waits for consent, every session. ",{"type":22,"tag":41,"props":50,"children":52},{"className":51},[],[53],{"type":27,"value":54},"chrome.tabCapture",{"type":27,"value":56}," needs the user to invoke the extension first, and it sees one tab, not the desktop. Both die with the service worker in Manifest V3, so \"always on\" means keeping a worker alive that Chrome is actively trying to put to sleep.",{"type":22,"tag":23,"props":58,"children":59},{},[60],{"type":27,"value":61},"There is no silent path. That is not an oversight, it is the contract. An extension is a guest in someone else's browser, and Chrome treats pixels as the most user-owned resource there is.",{"type":22,"tag":29,"props":63,"children":65},{"id":64},"what-vendors-ship-instead",[66],{"type":27,"value":67},"What vendors ship instead",{"type":22,"tag":23,"props":69,"children":70},{},[71],{"type":27,"value":72},"The workaround industry is real. DOM snapshots streamed on a timer. MutationObserver diffs reassembled server-side into something that plays back like video. Canvas readback where the page allows it. It demos well. Then you deploy it and find what it misses: cross-origin iframes, native file dialogs, the address bar, PDFs in the built-in viewer, every other app on the desktop, and the user who drags the tab into a new window.",{"type":22,"tag":74,"props":75,"children":76},"table",{},[77,96],{"type":22,"tag":78,"props":79,"children":80},"thead",{},[81],{"type":22,"tag":82,"props":83,"children":84},"tr",{},[85,91],{"type":22,"tag":86,"props":87,"children":88},"th",{},[89],{"type":27,"value":90},"The policy asks for",{"type":22,"tag":86,"props":92,"children":93},{},[94],{"type":27,"value":95},"An extension can see",{"type":22,"tag":97,"props":98,"children":99},"tbody",{},[100,114,127,140],{"type":22,"tag":82,"props":101,"children":102},{},[103,109],{"type":22,"tag":104,"props":105,"children":106},"td",{},[107],{"type":27,"value":108},"The whole desktop",{"type":22,"tag":104,"props":110,"children":111},{},[112],{"type":27,"value":113},"One tab, with consent, per session",{"type":22,"tag":82,"props":115,"children":116},{},[117,122],{"type":22,"tag":104,"props":118,"children":119},{},[120],{"type":27,"value":121},"Native dialogs and pickers",{"type":22,"tag":104,"props":123,"children":124},{},[125],{"type":27,"value":126},"Nothing",{"type":22,"tag":82,"props":128,"children":129},{},[130,135],{"type":22,"tag":104,"props":131,"children":132},{},[133],{"type":27,"value":134},"Cross-origin iframes",{"type":22,"tag":104,"props":136,"children":137},{},[138],{"type":27,"value":139},"A hole in the snapshot",{"type":22,"tag":82,"props":141,"children":142},{},[143,148],{"type":22,"tag":104,"props":144,"children":145},{},[146],{"type":27,"value":147},"Recording that survives a restart",{"type":22,"tag":104,"props":149,"children":150},{},[151],{"type":27,"value":152},"A service worker Chrome wants to sleep",{"type":22,"tag":29,"props":154,"children":156},{"id":155},"cant-policy-just-force-it",[157],{"type":27,"value":158},"Can't policy just force it?",{"type":22,"tag":23,"props":160,"children":161},{},[162,164,170],{"type":27,"value":163},"Admins ask the reasonable next question: we manage these machines, can't policy grant the permission. Policy can force-install the extension, pin its version, hide the uninstall button. It cannot grant capture consent on the user's behalf. The picker is rendered by the browser, above the extension's reach, deliberately. There are narrow loosenings, kiosk modes, allowlisted origins for ",{"type":22,"tag":41,"props":165,"children":167},{"className":166},[],[168],{"type":27,"value":169},"getDisplayMedia",{"type":27,"value":171},", and none of them add up to a silent, always-on recorder driven by an extension.",{"type":22,"tag":29,"props":173,"children":175},{"id":174},"what-it-actually-takes",[176],{"type":27,"value":177},"What it actually takes",{"type":22,"tag":23,"props":179,"children":180},{},[181],{"type":27,"value":182},"Recording is a compositor-level capability. To make it a policy decision instead of a user decision, you have to own the compositor, which means owning the browser. On a Chromium fork the capture pipeline is yours: gated by enterprise policy, scoped to the profiles and sites the policy names, with a consent surface you design and disclose honestly. That is the line between an extension product and a browser product, and recording is usually the first requirement that makes a buyer cross it.",{"type":22,"tag":23,"props":184,"children":185},{},[186],{"type":27,"value":187},"None of this is extension-bashing. For most policy an extension is the right tool, cheaper to deploy and easier to trust. The ceiling is real for the rest. Part 2 is Chrome sync and MDM scoping, where the failure is quieter and the data walks out the front door.",{"title":7,"searchDepth":189,"depth":189,"links":190},2,[191,192,193,194],{"id":31,"depth":189,"text":34},{"id":64,"depth":189,"text":67},{"id":155,"depth":189,"text":158},{"id":174,"depth":189,"text":177},"markdown","content:blog:extension-ceiling-1-mandatory-screen-recording.md","content","blog\u002Fextension-ceiling-1-mandatory-screen-recording.md","blog\u002Fextension-ceiling-1-mandatory-screen-recording","md",{"_path":202,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":203,"description":204,"heading":205,"date":206,"minutes":207,"series":13,"part":189,"parts":15,"summary":208,"short":209,"body":210,"_type":195,"_id":337,"_source":197,"_file":338,"_stem":339,"_extension":200},"\u002Fblog\u002Fextension-ceiling-2-chrome-sync-and-mdm-scoping","The extension ceiling, part 2: Chrome sync and MDM scoping","Part 1 was about a capability extensions visibly lack. This one is quieter. Nothing fails, nothing prompts, nobody notices anything. Chrome sync just does its job, and its job is moving browser state off the machine.","Chrome sync and MDM scoping","2026-04-09",6,"Chrome sync will happily carry corporate state into personal profiles. What the MDM scoping rules actually cover, item by item.","Chrome sync moves passwords, history, and open tabs to whatever Google account is signed in, corporate or not. The policies that scope it manage the profile, not the person, and an extension cannot see the channel at all.",{"type":19,"children":211,"toc":331},[212,216,222,227,232,238,243,292,304,310,315,321,326],{"type":22,"tag":23,"props":213,"children":214},{},[215],{"type":27,"value":204},{"type":22,"tag":29,"props":217,"children":219},{"id":218},"what-sync-actually-carries",[220],{"type":27,"value":221},"What sync actually carries",{"type":22,"tag":23,"props":223,"children":224},{},[225],{"type":27,"value":226},"Sync is not one stream, it is a set of typed channels: bookmarks, history, open tabs, passwords, autofill including addresses and payment cards, extensions, settings, and a handful of smaller types. Sign into Chrome with any Google account and every enabled type starts flowing to that account. Encrypted to Google by default, end to end only if the user sets a passphrase, which almost nobody does.",{"type":22,"tag":23,"props":228,"children":229},{},[230],{"type":27,"value":231},"Now picture the standard mixed setup: a corporate machine, a managed work profile, and an employee who signs a personal Gmail into that profile, or just uses the personal profile sitting one click away in the profile switcher. The passwords saved against internal apps, the history of every internal hostname, the open tabs with ticket numbers in their titles, all of it syncs up to the personal account and back down to a personal laptop at home. No upload event, no file, nothing a DLP rule would classify as exfiltration. The traffic is TLS to google.com, which every allowlist on earth permits.",{"type":22,"tag":29,"props":233,"children":235},{"id":234},"the-policies-item-by-item",[236],{"type":27,"value":237},"The policies, item by item",{"type":22,"tag":23,"props":239,"children":240},{},[241],{"type":27,"value":242},"Chrome Enterprise has real controls here, and they are narrower than people assume.",{"type":22,"tag":244,"props":245,"children":246},"ul",{},[247,259,270,281],{"type":22,"tag":248,"props":249,"children":250},"li",{},[251,257],{"type":22,"tag":41,"props":252,"children":254},{"className":253},[],[255],{"type":27,"value":256},"SyncDisabled",{"type":27,"value":258}," kills sync for the profiles it reaches. Total and blunt, and users notice their bookmarks stopped following them.",{"type":22,"tag":248,"props":260,"children":261},{},[262,268],{"type":22,"tag":41,"props":263,"children":265},{"className":264},[],[266],{"type":27,"value":267},"SyncTypesListDisabled",{"type":27,"value":269}," turns off individual types. Most teams disable passwords and autofill, keep bookmarks, and call it scoped.",{"type":22,"tag":248,"props":271,"children":272},{},[273,279],{"type":22,"tag":41,"props":274,"children":276},{"className":275},[],[277],{"type":27,"value":278},"BrowserSignin",{"type":27,"value":280}," can block browser sign-in entirely, or force it so the profile is always managed.",{"type":22,"tag":248,"props":282,"children":283},{},[284,290],{"type":22,"tag":41,"props":285,"children":287},{"className":286},[],[288],{"type":27,"value":289},"RestrictSigninToPattern",{"type":27,"value":291}," is the precise one: a pattern over which accounts may sign in, usually anchored to the corporate domain.",{"type":22,"tag":23,"props":293,"children":294},{},[295,297,302],{"type":27,"value":296},"The catch is scope. A policy attaches to whatever is managed: the device, the browser install, or the account. Applied at the machine or browser level, ",{"type":22,"tag":41,"props":298,"children":300},{"className":299},[],[301],{"type":27,"value":289},{"type":27,"value":303}," does reach every profile on that install, and that is the strict setup. Applied through the managed account, it governs that account's profile and says nothing about the personal profile next to it. BYOD, unmanaged home installs of the same browser, and half-enrolled fleets fall through exactly this gap. The data follows the account, and the account is the one thing MDM does not own.",{"type":22,"tag":29,"props":305,"children":307},{"id":306},"where-the-extension-sits",[308],{"type":27,"value":309},"Where the extension sits",{"type":22,"tag":23,"props":311,"children":312},{},[313],{"type":27,"value":314},"Below all of it. There is no extensions API for sync. An extension can read bookmarks and history, but it cannot tell synced from local, cannot see the sync queue, and cannot observe a personal account attaching itself to the profile. Extension-based DLP does not have a degraded view of this channel. It has none.",{"type":22,"tag":29,"props":316,"children":318},{"id":317},"closing-it-for-real",[319],{"type":27,"value":320},"Closing it for real",{"type":22,"tag":23,"props":322,"children":323},{},[324],{"type":27,"value":325},"Strict policy gets you most of the way if you are willing to pay for it: force managed sign-in, restrict the pattern at the browser level, disable secondary profiles, accept the tickets. In our fork we went one layer further, because we could reach the layer policy cannot: the sync client itself. The browser knows which profile is corporate, so it can refuse to start typed channels for the wrong account, log what would have left, and leave personal Chrome on the same machine alone. That is not cleverness, it is just owning the code that runs.",{"type":22,"tag":23,"props":327,"children":328},{},[329],{"type":27,"value":330},"Part 3 needs no account and no API gap at all, only a keyboard shortcut: the incognito window.",{"title":7,"searchDepth":189,"depth":189,"links":332},[333,334,335,336],{"id":218,"depth":189,"text":221},{"id":234,"depth":189,"text":237},{"id":306,"depth":189,"text":309},{"id":317,"depth":189,"text":320},"content:blog:extension-ceiling-2-chrome-sync-and-mdm-scoping.md","blog\u002Fextension-ceiling-2-chrome-sync-and-mdm-scoping.md","blog\u002Fextension-ceiling-2-chrome-sync-and-mdm-scoping",{"newer":341,"older":342},{"_path":202,"title":203,"heading":205,"date":206},{"_path":343,"title":344,"heading":344,"date":345},"\u002Fblog\u002Fwhat-enterprise-browser-productivity-numbers-really-mean","What enterprise browser productivity numbers really mean","2025-12-03",1781170135582]