1. Overview
The SecureBrowser module implements BastionGuard’s hardened browsing mode based on Chromium Embedded Framework (CEF). It is intended for high-risk interactions such as banking logins and payment/checkout flows and enforces a strict navigation policy based on allowlisted domains (banks + optional payment providers) with a local warning interstitial for blocked destinations.
This module is used by the Secure Payments / Secure Browser entrypoints (e.g., the launcher that spawns BastionGuard-bankopener) and is designed to:
- Run in a dedicated process and enforce “single instance” behavior via a lockfile
- Initialize CEF with security-oriented command-line switches
- Restrict navigation to trusted bank/payment domains while preserving functional flows (SSO, iframes, redirects)
- Display a local HTTP warning page for blocked domains
- Apply certificate-error handling policy (bypass only for trusted domains)
- Provide robust backend fallback (Wayland → X11 retry, and final fallback to
xdg-open)
2. Major Subsystems
- CEF client handlers – request interception, lifecycle management, and UI watermark injection
- Domain allowlists – system + user JSON lists for banking and payments
- LocalWarningServer – local interstitial server used for blocked navigation warnings
- SettingsStore – runtime toggle controlling payments allowlist activation
- InstanceLock – lockfile-based singleton enforcement across processes
3. Local Warning Interstitial
SecureBrowser uses a local HTTP warning server to show a controlled interstitial page when navigation is blocked. The server is bound to a dedicated local IP and port:
- IP:
127.0.0.2 - HTTP port:
81
The server is started once per process via start_local_warning_server() and is kept alive using a global singleton pointer (g_warning_server). The current configuration runs HTTP only (no certificate material is provided).
Blocked navigations are redirected to:
http://127.0.0.2:81/<blocked_host>
4. Single-instance Enforcement
4.1 In-process Guard
A process-level guard prevents duplicate initialization in the same process:
static std::atomic<bool> g_cef_started{false};
If a duplicate request occurs, SecureBrowser falls back to the system browser (xdg-open).
4.2 Cross-process Lockfile
To enforce a single active instance across processes, SecureBrowser uses a lockfile:
/tmp/BastionGuard-bankopener.lock
The InstanceLock utility:
- Creates/opens the lock file with mode
0600 - Attempts a non-blocking exclusive flock (
LOCK_EX | LOCK_NB) - If acquired, writes the process PID into the file and fsyncs it
- Releases the lock on destruction, but intentionally does not remove the file (acts as a beacon and avoids races)
If the lock cannot be acquired, SecureBrowser assumes another instance is active and falls back to xdg-open.
5. Domain Allowlist Loading and Normalization
5.1 Host Parsing and Canonicalization
URLs are normalized to a host using:
- Strip scheme (
://) and path suffix - Remove credentials (
user@host) if present - Lowercase
- Remove leading dot
- Strip
www. - Strip port (e.g.,
:443)
The canonicalization pipeline is implemented by host_from_url() and clean_domain().
5.2 JSON Allowlist Format Flexibility
Allowlist files are loaded by load_list_file(path, out), which supports multiple JSON shapes:
- Object with
listarray - Object with
domainsarray - Object with
whitelistarray - Root-level array of strings
Binary reads are sanitized by removing embedded NUL bytes before JSON parsing to reduce parsing failures on corrupted files.
5.3 Banking Trusted Domains
Trusted banking domains are initialized lazily and cached in-memory:
- System list:
/usr/share/BastionGuard/data/bank/banks.json - User override:
~/.config/BastionGuard/whitelist.json
The cache uses an unordered_set for constant-time membership checks.
Matching behavior:
- Direct match: host must equal an allowlisted domain
- Parent-domain match: subdomains are allowed if any parent suffix matches the allowlist (e.g.,
login.sub.bank.com→ allow ifbank.comis allowlisted)
5.4 Payment Domains (Optional)
Payment-domain allowlisting is an optional subsystem controlled by:
SettingsStore::get_secure_payments_enabled()
If disabled, payment-domain checks always return false. If enabled, the module initializes a dedicated cache from:
- System list:
/usr/share/BastionGuard/data/payments/payments.json - User override:
~/.config/BastionGuard/payments.json
Matching supports both direct and parent-domain suffix matches (e.g., sub.gateway.paypal.com → paypal.com).
6. CEF Client Handlers
6.1 SimpleHandler Responsibilities
The CEF client implementation (SimpleHandler) provides:
- LifeSpan handling – tracks open browser windows and exits message loop when all are closed
- Display handling – injects a UI watermark on title change
- Request handling – enforces navigation policy and certificate error policy
6.2 Window Lifecycle
OnAfterCreated()adds the browser instance to an internal vectorOnBeforeClose()removes the instance and callsCefQuitMessageLoop()when no windows remain
This guarantees deterministic shutdown of the CEF message loop and supports clean process termination.
6.3 Watermark Injection
On title changes, SecureBrowser injects a fixed-position watermark into the main document using JavaScript. The watermark is appended only once (guarded by a DOM element ID) and is non-interactive (pointer-events: none).
The watermark serves as a user-visible assurance indicator that the user is inside the Secure Browser session.
7. Navigation Policy and Trusted Session Model
7.1 Policy Goals
The navigation policy is designed to:
- Strictly prevent user-driven navigation from leaving trusted banking/payment domains
- Allow necessary embedded resources during trusted sessions (iframes, SSO redirects, CDNs, CAPTCHA)
- Provide a safe interstitial when blocked destinations are requested
7.2 Localhost Allow Rule
Requests to local infrastructure are always allowed to prevent self-blocking of the warning server:
127.0.0.2,127.0.0.1,localhost
7.3 Trusted Session State Machine
SecureBrowser maintains a session-level trust flag:
g_session_trusted– set to true once the main frame successfully loads a trusted bank/payment entry domaing_entry_host– stores the entry host for diagnostics
Policy rules:
- Session entry: if the navigation is for the main frame and the host is bank/payment allowlisted, the session is marked trusted and navigation is allowed.
- Subframe permissive mode: while in a trusted session, all subframe loads are allowed to preserve real-world banking flows (embedded auth, iframes, integrations).
- Non user-gesture main-frame: while in a trusted session, main-frame navigations without user gesture are allowed (technical redirects and SSO flows).
- User-initiated main-frame exits: while in a trusted session, if the user clicks and attempts to navigate the main frame to a non-allowlisted host, the navigation is blocked and redirected to the warning interstitial.
- No trusted session: before session trust is established, only allowlisted bank/payment hosts are allowed. All other requests are blocked and redirected to the warning interstitial.
Blocked requests are redirected by loading the local warning URL in the main frame and returning true to cancel the original navigation.
8. Certificate Error Policy
Certificate errors are handled in OnCertificateError(). The policy is:
- Bypass certificate errors only for bank/payment allowlisted hosts
- Block certificate errors for all non-trusted hosts and redirect to the local warning interstitial
For trusted hosts, the handler calls callback->Continue(). For non-trusted hosts, it calls callback->Cancel().
This design prevents users from being trained to click through TLS errors on unknown domains while maintaining operational usability for allowlisted targets.
9. CEF Process Configuration (ClamApp)
9.1 Command-line Hardening
CEF command-line switches are applied in ClamApp::OnBeforeCommandLineProcessing(). Key security and isolation switches include:
incognitodisable-extensionsdisable-syncdisable-component-updatedisable-print-previewdisable-background-networkingdisable-crash-reporter,disable-breakpadno-default-browser-check,no-first-run
Feature toggles are applied via AppendSwitchWithValue to avoid collisions between multiple enable/disable-features settings:
disable-features=TranslateUIenable-features=UseOzonePlatform,VaapiVideoDecoder,UseSkiaRenderer,DnsOverHttps
9.2 TLS Trust Store Configuration
SecureBrowser pins certificate bundle configuration to the system CA bundle:
ssl-certificates-file=/etc/ssl/certs/ca-certificates.crt
Additionally, environment variables are set before CEF initialization to maximize trust store compatibility across Chromium/NSS consumers:
SSL_CERT_DIR,SSL_CERT_FILENSS_CERT_DIR,NSS_CERT_FILE
9.3 DNS-over-HTTPS (DoH)
The sandbox enables DNS-over-HTTPS in automatic mode and defines a default server list:
dns-over-https-mode=automaticdns-over-https-servers=https://cloudflare-dns.com/dns-query,https://dns.google/dns-query
This can reduce dependency on local DNS interception (e.g., dnsmasq) for this hardened browsing context.
9.4 Graphics and Platform Backend Selection (Wayland/X11)
The module supports both Wayland and X11 environments. Backend selection logic:
- If
BastionGuard_CEF_BACKENDis explicitly set, it is honored (waylandorx11) - Otherwise, presence of
WAYLAND_DISPLAYselects Wayland
The ozone platform is set accordingly:
- Wayland:
ozone-platform=wayland+enable-webrtc-pipewire-capturer - X11:
ozone-platform=x11
10. CEF Initialization and Fallback Strategy
10.1 Resource and Cache Paths
CEF runtime directories:
- Resources:
/usr/share/BastionGuard/cef - Locales:
/usr/share/BastionGuard/cef/locales - Cache:
~/.cache/BastionGuard/cef_sandbox
The cache directory is created best-effort using std::filesystem::create_directories().
10.2 Initialization Attempts
CEF initialization is attempted via an internal helper try_init_cef(backend):
- Initial attempt: “auto” (no forced backend)
- If it fails and
WAYLAND_DISPLAYis set, the module unsets Wayland and retries withx11 - If initialization fails definitively, the module falls back to
xdg-open
CEF is configured with:
no_sandbox = true(CEF/Chromium sandbox disabled at CEF level; isolation is enforced by policy and runtime constraints)log_severity = warning- Explicit resource/locales/cache paths
11. Window Creation and Message Loop
On successful initialization:
- A
CefBrowserViewis created with JavaScript and WebGL enabled - A top-level CEF window is created via a custom
CefWindowDelegate - The message loop runs using
CefRunMessageLoop() - On exit, the module calls
CefShutdown()and resetsg_cef_started
If CreateBrowserView() returns null, the module performs a controlled fallback: xdg-open + CefShutdown().
12. Security and Operational Considerations
- Allowlist enforcement: primary defense relies on strict allowlists with parent-domain matching; list hygiene is critical.
- Trusted session permissiveness: subframes are allowed during trusted sessions to support real flows; this is a deliberate usability/security trade-off.
- User-initiated exit blocking: main-frame clicks to non-trusted domains are blocked once a trusted session is established.
- Certificate bypass scope: TLS error bypass is limited to allowlisted bank/payment hosts; all others are blocked and warned.
- Local interstitial security: the warning server is bound to loopback; blocking logic explicitly allows localhost to prevent self-denial.
- Process singleton: lockfile-based singleton reduces multi-instance confusion and constrains attack surface.
- Fallback behavior: when hardening cannot be guaranteed (init failure / existing instance), the module degrades to system browser rather than partially working insecurely.
- CEF sandbox flag:
no_sandbox=trueindicates Chromium’s internal sandbox is disabled; if stronger isolation is required, consider enabling CEF sandbox and/or running the process under OS-level confinement (e.g., namespaces, seccomp, bubblewrap).
In summary, SecureBrowser delivers a hardened CEF-based browsing session centered on allowlist-driven navigation control, local warning interstitials, and conservative TLS error handling, with robust startup/compatibility fallbacks for real-world Linux desktop environments.