1. Overview
This module implements the Secure Payments domain gate for BastionGuard. Its role is to decide whether a given navigation request must be opened inside the SecureBrowser (CEF sandbox) or delegated to the system browser (gio open / xdg-open), based on a user-managed allowlist stored in payments.json.
The gate is enforced by the SecureList API. It performs:
- Path resolution for the user configuration file (
~/.config/BastionGuard/payments.json) - Domain normalization and extraction from URLs
- Allowlist matching (exact domain and subdomain suffix matching)
- Runtime enable/disable control through SettingsStore
- Secure browser launch using
SecureBrowser::open()
2. Components and Dependencies
- SecureBrowser – launches the sandboxed CEF-based browser UI.
- SettingsStore – provides the runtime toggle for Secure Payments enablement.
- nlohmann::json – parses
payments.json. - glibmm – uses
Glib::get_user_config_dir()for XDG-compliant config resolution. - POSIX / shell tools – invokes
gio open(Wayland-friendly) orxdg-openas fallback.
3. Configuration File: payments.json
3.1 Location
The allowlist is read exclusively from the user configuration path:
~/.config/BastionGuard/payments.json
The module intentionally loads only the list field and ignores other metadata keys (e.g., version, enabled, updated_at) to keep the decision logic narrowly scoped and stable.
3.2 Expected Structure
The JSON is expected to be an object containing an array key list:
{
"list": [
"paypal.com",
"stripe.com",
"example-payment-gateway.tld"
]
}
If payments.json is missing, unreadable, invalid, or lacks a valid list array, the allowlist will be treated as empty and Secure Payments decisions will fall back to the system browser.
4. Domain and URL Normalization
4.1 clean_domain()
Domains are normalized through SecureList::clean_domain() to reduce false mismatches and ensure consistent comparison. Normalization rules include:
- Trim leading/trailing whitespace
- Force lowercase
- Remove leading dot (e.g.,
.paypal.com→paypal.com) - Strip
www.prefix (e.g.,www.stripe.com→stripe.com) - Remove port suffix (e.g.,
example.com:443→example.com)
This function is applied both to entries loaded from payments.json and to runtime extracted hosts.
4.2 extract_domain()
For URL inputs, the module extracts a host component using a permissive regular expression supporting:
- Optional scheme (e.g.,
https://) - Optional userinfo (e.g.,
user:pass@) - Host extraction without port/path/query/fragment
Extracted values are normalized via clean_domain(). If extraction fails, the module degrades safely by opening the URL in the system browser.
5. Allowlist Loading and Caching
5.1 List-only Loader
The internal loader reads the file and populates a normalized vector of domains from:
payments.json["list"](string entries only)
Non-string entries are skipped. Invalid JSON is handled without crashing; diagnostics are written to stderr.
5.2 Lazy Static Cache
The allowlist is loaded once per process lifecycle through SecureList::payments_cache_only(), which uses:
- A static boolean
initguard - A static vector
cachedholding the normalized allowlist
This approach minimizes IO overhead during frequent URL checks and keeps the hot-path stable. The trade-off is that runtime changes to payments.json will not be picked up until process restart (unless the surrounding application provides a reload mechanism).
6. Matching Logic
6.1 ends_with()
A case-insensitive suffix comparator is provided to support subdomain matching.
6.2 matches_payment_domain()
The decision uses two matching modes against the normalized allowlist:
- Exact match:
host == domain - Subdomain match:
hostends with"." + domain(e.g.,sub.paypal.commatchespaypal.com)
This behavior ensures that payment providers and their subdomains remain covered without requiring redundant list entries.
7. Runtime Gate: SettingsStore Integration
7.1 is_payment_domain()
SecureList::is_payment_domain() is the policy entry point. Before any allowlist matching occurs, it verifies that Secure Payments is enabled at runtime:
SettingsStore::get_secure_payments_enabled()
If the setting is disabled, the function returns false immediately, effectively bypassing the Secure Payments gate even if the domain is in the allowlist.
8. Routing Decision API
8.1 open_if_allowed(url, argc, argv)
This is the primary operation exposed by the module. It enforces the following routing flow:
- Validate input URL (empty URL is rejected and returns
false). - Extract domain via
extract_domain(). - If domain extraction fails:
- Open URL in system browser
- Return
false
- If domain is allowed by
is_payment_domain(domain):- Launch SecureBrowser:
SecureBrowser::open(url, argc, argv) - Return
true
- Launch SecureBrowser:
- Otherwise:
- Open URL in system browser
- Return
false
The return value communicates whether the secure sandbox path was used (true) or the system browser fallback was used (false).
9. System Browser Fallback
9.1 open_system_browser()
When Secure Payments does not apply, URLs are opened via the system browser using:
gio openwhen a Wayland session is detected (WAYLAND_DISPLAYset) andgiois available.xdg-openas a generic fallback.
This strategy improves compatibility across desktop environments and display servers while keeping dependencies minimal.
10. Operational and Security Considerations
- Allowlist integrity: only the
listfield is trusted for decision-making. Invalid or missing lists default to “deny” (system browser fallback). - Input normalization: trimming, lowercasing, removal of
www.and ports reduces bypass via formatting variations. - Subdomain policy: suffix matching allows
*.payment-domain.tldby design; ensure allowlist entries are curated to avoid overly broad domains. - Cache behavior: allowlist is loaded once per process. If live reload is required, integrate an invalidation strategy (mtime check or explicit reload call).
- Shell execution:
open_system_browser()usessystem()with the URL embedded in a quoted string; if URLs can be attacker-controlled, consider migrating toGio::AppInfo::launch_default_for_uri()or equivalent to avoid shell interpretation. - Feature gating: enforcement is controlled by
SettingsStore::get_secure_payments_enabled(), ensuring the user can disable Secure Payments without modifying the allowlist file.
11. Typical Integration Flow
- User enables Secure Payments in the Settings UI (persisted via
SettingsStore). - Application routes sensitive checkout/payment URLs through
SecureList::open_if_allowed(). - If the domain is allowlisted in
payments.json, BastionGuard launchesSecureBrowser(CEF sandbox). - Otherwise, the URL is opened via the default system browser for standard browsing.