1. Overview
The BankOpener module implements BastionGuard’s trusted-domain decision layer for opening URLs. It evaluates whether a given URL belongs to an allowlisted banking domain (or, optionally, to an allowlisted payment provider domain when Secure Payments is enabled) and routes the URL accordingly:
- Trusted → open inside the hardened CEF sandbox (
SecureBrowser::open()) - Not trusted → open in the system browser via
gio openorxdg-open
BankOpener is used by the BastionGuard-bankopener launcher executable and serves as a security-critical gatekeeper: correctness of parsing and allowlist matching directly affects the Secure Browser enforcement model.
2. Entry Point: open_if_trusted()
bool BankOpener::open_if_trusted(const std::string& url)
This method is the module’s primary API. Its behavior is:
- Validate that the URL is not empty
- Extract the domain (host) from the URL
- Evaluate whether the host is a trusted bank domain
- Evaluate whether the host is a trusted payment domain (only if Secure Payments is enabled)
- If trusted (bank OR payments), invoke
SecureBrowser::open(url)and returntrue - If not trusted, open the URL using the system browser and return
false
The function logs diagnostics to stdout/stderr using gettext-translated strings.
3. Domain Extraction and Normalization
3.1 extract_domain()
std::string BankOpener::extract_domain(const std::string& url)
Domain extraction uses a regular expression that tolerates optional scheme and optional www.:
- Matches
http://orhttps://optionally - Matches
www.optionally - Captures the host up to the first
/or:
After extraction:
www.is stripped if present- The host is lowercased
If extraction fails, an empty string is returned and the caller treats this as a non-trusted URL.
3.2 clean_domain()
std::string BankOpener::clean_domain(const std::string& d)
Domain canonicalization applies:
- Strip leading dot (e.g.,
.example.com) - Strip
www. - Lowercase conversion
This normalization is applied before allowlist matching to reduce mismatches caused by common host formatting.
4. Suffix Matching Utility
4.1 ends_with()
bool BankOpener::ends_with(const std::string& s,
const std::string& suffix)
Suffix comparison is case-insensitive and is used to support subdomain matching. This enables logic such as:
login.bank.commatches allowlist entrybank.com
5. Trusted Bank Domain Resolution
5.1 is_trusted_domain()
bool BankOpener::is_trusted_domain(const std::string& domain)
Trusted bank domains are built by merging multiple JSON sources:
- User list:
~/.local/share/BastionGuard/banks.json - System baseline:
/usr/share/BastionGuard/data/bank/banks.json - User whitelist override:
~/.config/BastionGuard/whitelist.json
Each file is optional; missing files are skipped. Failures to open or parse JSON produce warnings but do not abort.
5.2 Supported JSON Formats
The loader supports several structures to maximize compatibility with different feeds:
- MISP-like object format: object containing
listarray- Array items may be strings
- Or objects with
valuefields
- Array format: a root JSON array where elements may be:
- Strings (direct domains)
- Objects containing
domainsarrays (first element used)
All extracted values are normalized via clean_domain() prior to insertion.
5.3 Matching Rules
After domain normalization (dnorm), each allowlisted domain d is matched with a permissive subdomain strategy:
- Direct match:
dnorm == d - Subdomain match:
dnormends with.+d - Reverse suffix match:
dends with.+dnorm
This accepts both “host is subdomain of allowlisted domain” and a limited “allowlisted entry is subdomain of host” pattern, providing flexibility for list granularity.
If no domains are loaded at all, the function returns false and logs a warning.
6. Trusted Payment Domain Resolution
6.1 Feature Gate (SettingsStore)
bool BankOpener::is_payment_domain(const std::string& domain)
Payment allowlisting is active only if the Secure Payments feature toggle is enabled:
SettingsStore::get_secure_payments_enabled()
If disabled, is_payment_domain() returns false immediately.
6.2 Payment Domain Sources
Payment allowlist domains are loaded from:
- User list:
~/.config/BastionGuard/payments.json - System baseline:
/usr/share/BastionGuard/data/payments/payments.json
Domains are loaded once and cached using static function-local variables:
static bool initializedstatic std::vector<std::string> cached
This design minimizes disk I/O during repeated checks (e.g., during multiple URL opens within one process).
6.3 Supported JSON Formats
The payments loader accepts several formats:
- Object with
listarray (elements may be strings or objects withvalue) - Object with
domainsarray - Object with
whitelistarray - Root array of strings
- Root array of objects containing
domainsarrays (first element used)
Extracted domains are normalized using clean_domain().
6.4 Matching Rules
Matching uses the same suffix strategy as banking domains:
dnorm == ddnormends with.+ddends with.+dnorm
If the cached list is empty, the method returns false.
7. System Browser Fallback
When the domain is not trusted (neither bank nor payment), BankOpener opens the URL using the default system browser. The selection is:
- If a Wayland session is detected (
WAYLAND_DISPLAY) andgiois available:gio open "<url>" - Otherwise:
xdg-open "<url>"
The command is executed via std::system(). This is intentionally a last-resort path and is used only when the security sandbox is not applicable.
8. Localization and Diagnostics
BankOpener uses gettext localization (<glibmm/i18n.h>) for all user-visible CLI diagnostics. This keeps messages consistent with the rest of the BastionGuard user experience.
9. Security and Design Considerations
- Security-critical routing: BankOpener is a trust gate. Errors in domain extraction or allowlist evaluation can lead to unsafe URLs entering the secure sandbox or trusted URLs being incorrectly rejected.
- Normalization scope: the current normalization removes
www.and lowercases but does not remove ports insideextract_domain()beyond the regex host capture. Downstream code relies on the regex capture excluding “:port”. - Suffix matching flexibility: permissive suffix checks enable subdomain flows but can broaden trust scope if allowlists contain overly generic domains.
- Static caching: payment domains are cached per-process; if the user edits payments.json at runtime, changes will not be picked up unless the process restarts (or caching is extended with reload logic).
- Shell execution fallback: system browser fallback relies on shell invocation. Although URLs are quoted, upstream components should treat URL input as untrusted and validate where appropriate.
In summary, BankOpener provides BastionGuard’s URL trust classification and routing logic by merging multiple domain sources, applying normalization and suffix matching, and delegating trusted destinations to the hardened SecureBrowser while using a controlled fallback for all others.