BankOpener (Trust Gate and URL Routing)

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 open or xdg-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:

  1. Validate that the URL is not empty
  2. Extract the domain (host) from the URL
  3. Evaluate whether the host is a trusted bank domain
  4. Evaluate whether the host is a trusted payment domain (only if Secure Payments is enabled)
  5. If trusted (bank OR payments), invoke SecureBrowser::open(url) and return true
  6. 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:// or https:// 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.com matches allowlist entry bank.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 list array
    • Array items may be strings
    • Or objects with value fields
  • Array format: a root JSON array where elements may be:
    • Strings (direct domains)
    • Objects containing domains arrays (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: dnorm ends with . + d
  • Reverse suffix match: d ends 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 initialized
  • static 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 list array (elements may be strings or objects with value)
  • Object with domains array
  • Object with whitelist array
  • Root array of strings
  • Root array of objects containing domains arrays (first element used)

Extracted domains are normalized using clean_domain().


6.4 Matching Rules

Matching uses the same suffix strategy as banking domains:

  • dnorm == d
  • dnorm ends with . + d
  • d ends 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) and gio is 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 inside extract_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.