flutter_cef security hardening: nav scheme allowlist + ad-hoc compile-gate#1
Merged
Conversation
Host-settable allow-list of URL schemes the page may navigate to, enforced in the renderer's OnBeforeBrowse so it covers the initial load, programmatic navigate(), in-page link clicks, and redirects. `about:` is always permitted; default (null) preserves allow-all, so this is opt-in and non-breaking. Plumbed end-to-end: CefWebView(allowedSchemes:) -> controller.create lowercased CSV -> FlutterCefPlugin -> CefWebSession --allowed-schemes= argv -> cef_host g_allowed_schemes. Lets an embedder keep an untrusted page off file:/data:/chrome:, which matters when the host can drive navigation programmatically. Bumps to 0.1.2. cef_host rebuilt clean (0 errors); 94 Dart tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mock keychain + basic password store and the Mach-port peer-validation bypass are dev/ad-hoc conveniences that must not ship in a signed release. Put all three behind a new CEF_HOST_ADHOC compile flag (ON by default, so dev/CI builds stay byte-identical). A signed release builds with -DCEF_HOST_ADHOC=OFF: real Keychain via OSCrypt + enforced Mach-port peer validation, which then require correct inside-out Developer-ID signing of the cef_host tree. - CMakeLists: option(CEF_HOST_ADHOC ON) + target_compile_definitions. - main.mm: #ifdef-gate the command-line switches and the setenv bypass; the single-process switch (not a security shortcut) stays ungated. - build_cef_host.sh: honors CEF_HOST_ADHOC=OFF and documents it. Verified both paths compile clean (ON and OFF, 0 errors). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CEF implements loadHtmlString as a data: URL navigation and loadFile as a
file: one — both funnel through navigate() -> kOpNavigate -> OnBeforeBrowse.
So a `{http, https}` allowlist refused the host's own content-injection
APIs (the data:/file: load was cancelled), e.g. loadHtmlString stopped
rendering.
Give those host-trusted loads a dedicated path that bypasses the allowlist:
new kOpLoadTrusted opcode -> DoNavigateTrusted sets a one-shot
g_skip_allowlist_once immediately before LoadURL, consumed by the next
OnBeforeBrowse (set + read on the same CEF UI thread, so no race). navigate(),
the initial load, in-page clicks, and redirects stay gated. The host chose
loadHtmlString/loadFile content, so it isn't subject to the page allowlist.
Plumbed end-to-end: loadHtmlString/loadFile -> _loadTrusted -> 'loadTrusted'
channel method -> CefWebSession.loadTrusted -> kOpLoadTrusted. Dartdoc +
CHANGELOG document the exemption; the controller test now asserts they route
through loadTrusted (NOT the gated navigate). analyze clean; 94 tests pass;
cef_host rebuilt clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lock the demo browser's CefWebView to allowedSchemes: {http, https} and add a
toolbar "block test" button that attempts a file:// navigation — which the
renderer refuses (the page stays put), while loadHtmlString (the IME test page)
still loads since it is host-trusted content. Exercises the allowlist + the
content-load exemption end-to-end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rame only
Adversarial audit of the allowlist found two real issues in the prior
trusted-load fix:
HIGH — the one-shot g_skip_allowlist_once was stealable. LoadURL does not
invoke OnBeforeBrowse synchronously; it enqueues the navigation and the
callback arrives as a later UI task. So the global flag stayed armed across
a gap, and a page-initiated navigation to a blocked scheme already queued
(setTimeout location change, meta-refresh, pending redirect) could have its
OnBeforeBrowse run first and consume the host's exemption — a full allowlist
bypass. Replace the bool with g_trusted_pending (a multiset of exact URLs):
DoNavigateTrusted arms the specific URL, OnBeforeBrowse exempts ONLY a
matching main-frame request and consumes that one entry. A page nav to a
different URL can't steal it; a redirect of a trusted load carries a
different URL and stays gated. Only arm when an allowlist is set (immutable
after startup) so the set doesn't accumulate when the feature is off.
MEDIUM — the gate ran on every frame, so an {http,https} allowlist would
cancel legit cross-scheme SUBframes (blob:/data: iframes, PDF/video viewers,
ad frames), breaking real pages. Gate main-frame navigations only; subframes
can't change the view's top-level origin and are already policy-constrained
by Chromium.
The popup path (OnBeforePopup) was checked and is correctly suppressed
(never auto-navigates), so it is not a hole. cef_host rebuilt clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…EF_HOST_ADHOC=OFF) Make the signed-release build production-hardened, driven by the existing CEF_HOST_ADHOC flag so one switch flips the whole dev<->release posture. When CEF_HOST_ADHOC=OFF (signed release): - Chromium renderer/GPU sandbox ON: each helper subprocess initializes CefScopedSandboxContext (dlopens libcef_sandbox.dylib, calls cef_sandbox_initialize) before LoadInHelper, per cef_library_loader.h; the browser sets settings.no_sandbox = false. The browser process is never sandboxed on macOS — only the helpers, which is the canonical model. - Codesign uses entitlements.release.plist, which omits get-task-allow (notarization hard-fails with it; it's a local task-port priv-esc). JIT / unsigned-exec-memory / disable-library-validation are kept (CEF needs them). - (already) Mach-port bypass + mock keychain compiled out. Default CEF_HOST_ADHOC=ON is unchanged: unsandboxed, dev entitlements with get-task-allow — byte-identical dev/CI build, since the sandbox can't validate without proper Developer-ID signing. CMake passes CEF_HOST_ADHOC to the helper targets (gates process_helper.mm) and selects the entitlements file by flag. No static lib / no dist change — the minimal dist ships libcef_sandbox.dylib + the scoped-context wrapper. Verified: both configs compile clean (0 errors). The OFF artifact's helper signature carries the JIT entitlements with NO get-task-allow, links CefScopedSandboxContext, and bundles libcef_sandbox.dylib — a signing-ready production bundle. README security section + CHANGELOG updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two pre-bundling security-hardening slices for flutter_cef.
1. Navigation scheme allowlist (
581ac93)When set, the renderer's
OnBeforeBrowsecancels any navigation whose schemeisn't in the set — gating the initial load, programmatic
navigate(), in-pagelink clicks, and redirects.
about:is always permitted. Default (null)preserves allow-all, so it's opt-in and non-breaking. This is the
package-level primitive a host's own URL allowlist calls into; it keeps an
untrusted page off
file:/data:/chrome:when the host can drive navigationprogrammatically.
Plumbing:
CefWebView(allowedSchemes:)->CefWebController.create(lowercasedCSV) ->
FlutterCefPlugin->CefWebSession --allowed-schemes=->cef_hostg_allowed_schemes->OnBeforeBrowse.2. Gate ad-hoc-only CEF shortcuts behind
CEF_HOST_ADHOC(a7bc775)The mock keychain + basic password store and the Mach-port peer-validation
bypass are dev/ad-hoc conveniences that must not ship in a signed release. They
now sit behind a
CEF_HOST_ADHOCcompile flag (ON by default, so dev/CIbuilds are byte-identical). A signed release builds
-DCEF_HOST_ADHOC=OFF: realKeychain via OSCrypt + enforced peer validation — which then require correct
inside-out Developer-ID signing of the
cef_hosttree.build_cef_host.shhonors
CEF_HOST_ADHOC=OFF.Verification
flutter analyzeclean;flutter test94 pass (incl. 2 new allowlistplumbing tests).
cef_hostrebuilt clean on bothCEF_HOST_ADHOC=ONand=OFF(0 errors).allowedSchemes: {http, https}lock + a "tryfile://" button to exercise the block live (held for a manual pass beforethis lands).
🤖 Generated with Claude Code