BastionGuard/CHANGELOG.md
specialworld83 f0f913a209 Release 2.0
2026-07-15 10:52:22 +02:00

16 KiB

BastionGuard™ — Changelog


[1.2] — 2026-03-21


🇬🇧 English

New feature: Secure sandbox for payments and banking

Full implementation of the automatic isolation system for banking and payment sites. When the user navigates to a domain on the bank or payment list, BastionGuard intercepts the connection, opens an isolated CEF window on the exact URL, and restores the normal proxy when the window is closed.


Proxy MITM and interception (bastionguard_cef.cpp, tls_intercept.hpp)

  • Full URL preserved — the path and query string (e.g. /checkoutnow?token=…) are extracted from the HTTP request line after the TLS handshake and passed to the sandbox, instead of opening just https://host/. Fixes the PayPal checkout link case that was opening only paypal.com and ignoring the token.

  • No tab loop — separated "first interception" (Case 3, opens sandbox) from "session already active" (Case 2, serves block page only). Added atomic guard sandbox_try_open/sandbox_close for parallel requests arriving before the first do_intercept completes.

  • block_active_session() — new variant of block_secure_request used in Case 2: serves the TLS block page with no callback and no launch_sandbox, eliminating the root cause of the tab loop.

  • launch_sandbox is now blocking — removed the & from the shell command. std::system now waits for the child process to terminate, enabling session_close on return.

  • session_close(root) — new function that removes the session from g_sessions immediately when the sandbox is closed. SESSION_TTL raised from 30 seconds to 30 minutes (safety net for crashes, not the primary mechanism).

  • BG_SANDBOX=1 — environment variable passed to sandbox processes (BastionGuard-bankopener, BastionGuard-secure) to prevent LocalWarningServer from starting inside them, eliminating interference with internal redirects (e.g. the vendor's return URL).

  • SandboxApiServer — new HTTP server on 127.0.0.1:3131 handling GET /open-sandbox?url=…. Used by the JS banner injected into e-commerce pages to open the sandbox on the correct URL without requiring any browser extension.


Credit card form detection on e-commerce sites (card_form_injector.hpp)

  • CardFormTunnel — replaces TunnelSession for CONNECT connections to known e-commerce domains. Performs double TLS MITM (browser ↔ proxy ↔ server), buffers the first HTTP response (max 512 KB), analyses the HTML for card form patterns, injects the BastionGuard banner if found, then switches to transparent relay.

  • contains_card_form(html) — detects card fields via:

    • HTML5 autocomplete attributes (cc-number, cc-csc, cc-exp)
    • Common name/id values (card_number, cardnumber, cc_number, pan)
    • Placeholder text combined with CVV fields
    • SDK attributes from Stripe, Braintree, Adyen, Klarna
  • inject_into_response() — inserts the JS script before </head>, updates Content-Length, removes Transfer-Encoding: chunked if present.

  • JS banner — fixed at the bottom of the page, dark theme consistent with BastionGuard UI. "Pay securely" button calls SandboxApiServer; ✕ button dismisses the banner without opening the sandbox.

  • Three-level filter to minimise MITM overhead:

    1. should_inspect_for_card_forms(host) — MITM only on known e-commerce hosts (~80 sites: Amazon, eBay, Zalando, etc.); CDN and trackers excluded.
    2. is_checkout_path(path) — inside the MITM, buffers body only when the path contains checkout keywords (/checkout, /cart, /payment, /cassa, etc.). Product and catalogue pages use transparent relay with no overhead.
    3. contains_card_form(html) — injects only if the body actually contains a card form; order confirmation pages without forms produce no banner.

CEF sandbox (SecureBrowser.cpp)

  • Vendor return URL — after payment on PayPal (or any gateway), the final redirect back to the vendor's site is shown directly inside the sandbox. LocalWarningServer no longer interferes thanks to BG_SANDBOX=1.

  • Navigation in trusted session — once the user has visited a trusted domain (bank or payment), all subsequent redirects inside the sandbox are allowed without any block, including automatic redirects to the return URL. Only manual clicks toward external non-trusted domains show the inline block page.

  • load_block_page_inline(browser, host) — new private function of SimpleHandler that generates a block page as data:text/html,… directly in CEF, with no dependency on LocalWarningServer.

  • Conditional start_local_warning_server() — in SecureBrowser::open(), the LocalWarningServer is started only if the BG_SANDBOX environment variable is not set, preventing the sandbox process from starting a server that would interfere with its own navigation.


Block page (block_page.hpp)

  • Inline BastionGuard logoload_logo_base64() loads /usr/share/BastionGuard/data/logo.png and embeds it as a base64 data URI directly in the HTML. No network request required; works offline and inside the block page where the connection is already closed. Static cache: file is read once per process.

  • Emoji fallback — if the logo file is missing (non-standard installation or development environment), 🛡️ is used as before. No regressions.

  • Updated .shield CSS — supports both <img> (PNG logo) and <span> (emoji fallback): fixed size 72×72px, object-fit:contain, blue drop-shadow for both.


Bug fixes

Component Bug Fix
bastionguard_cef.cpp Thousands of tabs opened on PayPal interception Separated Case 2 and Case 3; atomic guard on launch_sandbox
bastionguard_cef.cpp Sandbox opened on https://paypal.com/ without token Extracted path from request line after TLS handshake
bastionguard_cef.cpp Session remained active after sandbox was closed session_close() + blocking launch_sandbox
SecureBrowser.cpp Red "Domain blocked" page shown on vendor return URL BG_SANDBOX=1 + free navigation in trusted session
SecureBrowser.cpp LocalWarningServer started inside the sandbox Conditioned on absence of BG_SANDBOX env var
tls_intercept.hpp do_intercept ignored path and query string Added on_url_ready callback with full URL

Compatibility

  • No changes to the public API of SecureBrowser, DomainFilter, BlockPage.
  • card_form_injector.hpp is a new optional header; targets that do not include it are unaffected.
  • BG_SANDBOX_API_PORT (default 3131) can be overridden at compile time via -DBG_SANDBOX_API_PORT=<port> in CMakeLists.txt.

New feature: Password Manager

A fully integrated password manager with a secure encrypted vault, built directly into BastionGuard. All credentials are stored locally — no cloud, no third-party service.


Encryption & Storage (PasswordVault.cpp, secrets.enc)

  • AES-256-GCM — all passwords are stored in a single encrypted file ~/.config/BastionGuard/password_manager/secrets.enc. The file contains the ciphertext, a 12-byte random nonce, and a 16-byte GCM authentication tag.

  • PBKDF2-SHA256 key derivation — the AES-256 key is derived from the master password using PBKDF2-SHA256 with 100,000 iterations and a 16-byte random salt regenerated on every write. Brute-force resistant even with direct file access.

  • Tamper detection — the GCM authentication tag is verified on every decrypt. Any modification to the ciphertext, nonce, salt, or tag makes the vault permanently unreadable. This is enforced at the cryptographic level.

  • master.json — stores only the PBKDF2 hash and salt of the master password. The plaintext is never written to disk. Comparison uses constant-time logic to prevent timing-based brute-force attacks.

  • Atomic writesecrets.enc is written via rename() on a .tmp file. No corruption can occur on crash or power loss mid-write.

  • Replaced libsecret per-entry storage — the previous architecture stored one secret per credential in GNOME Keyring via D-Bus. With 1,500 credentials this caused high gnome-keyring CPU usage and write failures due to D-Bus queue saturation. Now a single file write replaces all 1,500 D-Bus roundtrips.


Master password (PasswordManagerPage.cpp)

  • First-run wizard — on first open, the user creates a master password with a real-time strength indicator: Weak (red) / Medium (orange) / Strong (green). Minimum 8 characters; confirmation field required.

  • Re-lock / unlock — the RAM cache is cleared on lock; no plaintext remains in memory when the vault is closed. An async warm-up thread reloads all secrets from secrets.enc on the next unlock.


Performance (PasswordVault.cpp)

  • In-memory RAM cache — on unlock, all secrets are loaded from secrets.enc into an unordered_map<id, plaintext> protected by a std::mutex. Subsequent getSecretAsync() calls are answered from cache — zero D-Bus roundtrips per lookup.

  • Async warm-up — a background thread decrypts and populates the cache immediately after unlock. cacheReady_ flag set on completion; the UI shows "loading cache…" in the state badge during warm-up.

  • Bulk import O(n)addEntriesBulk() writes the index JSON once for the entire batch instead of once per entry (was O(n²)). Background thread persists secrets to secrets.enc with a single encrypt + write operation.

  • getSecretAsync() — three-level lookup:

    1. Cache hit → instant response via Glib::signal_idle
    2. Warm-up in progress → 50 ms polling via Glib::signal_timeout (max 5 s)
    3. Cache ready but id not found → fallback for edge cases

Browser import (PasswordImporter.cpp, PasswordImportDialog.cpp)

  • Supported formats — Chrome / Chromium, Edge (identical CSV format), Firefox (Lockwise export), Safari.

  • Auto-detect — browser identified from the CSV header line, case-insensitive, BOM-aware (UTF-8 BOM stripped). Robust to column order variations.

  • RFC 4180 parser — custom tokeniser handles quoted fields, embedded commas, escaped double-quotes (""), and Windows \r\n line endings.

  • Preview dialog — first 50 rows displayed before confirming; full import (all rows) runs in a background thread with a live progress bar.

  • Progress bar — updated via Glib::Dispatcher from the import thread. The UI never blocks regardless of import size.


Vault view & export (PasswordVaultView.cpp)

  • Vault view dialog — searchable table showing Name, Username, URL, Last updated. Full-text filter updates live as the user types.

  • Per-entry detail — clicking 👁 opens a detail popup with all fields (name, username, URL, password, notes, created/updated). Password field is selectable for copy-paste.

  • CSV export — runs on a background thread via Glib::Dispatcher. Format: name,url,username,password,notes — RFC 4180 compliant with proper quoting. File-save dialog with .csv filter.


UI & security indicators

  • Security banner — always visible at the top of the manager page. Explains AES-256-GCM protection and states that any file tampering renders the vault permanently unreadable.

  • State badge — top-right corner:

    • 🟢 Green (alert-success) when vault is locked — credentials safe, no plaintext in memory.
    • 🔴 Red (alert-danger) when vault is unlocked — vault open, credentials accessible in RAM.
  • Two-row toolbar — row 1: Unlock / Lock / state badge; row 2: + New / ⬆ Import / 🗄 Vault / ↺ Refresh / 🗑 Delete-all / 🔍 Search.

  • Delete-all — double confirmation dialog (WARNING → ERROR level) plus async background removal of secrets.enc.


Bug fixes

Component Bug Fix
PasswordVault.cpp secret_password_store fired 1,500× in a loop, saturating D-Bus queue Replaced per-entry libsecret with single AES-256-GCM file write
PasswordVault.cpp secret_password_lookup_sync blocked GTK4 main loop on "Show password" Replaced with in-memory RAM cache; fallback uses async secret_password_lookup
PasswordVault.cpp deleteAllEntries froze UI (N × clear_sync on main thread) Async background thread; index cleared atomically before keyring cleanup
PasswordImportDialog.cpp SEGV on import: dialog_->hide() called after delete dialog onImportDone no longer calls hide(); caller hides before delete
PasswordVault.cpp Passwords empty after re-lock/unlock (cache not populated) warmUpCache() thread started immediately after every unlock()

New feature: Dark mode & theme switcher


Theme files

  • BastionGuard-dark.css — full dark theme. Background #1a1d23, text #e4e7eb, cards #23272e, sidebar #16191f. Covers all UI components including sidebar, cards, buttons, inputs, badges, and separators.

  • BastionGuard.css — unchanged light theme, now the explicit default. Background #f8f9fa, text #212529.


Runtime switching (DashboardPage.cpp)

  • ☀ / 🌙 buttons — two flat icon buttons in the Dashboard footer, left of the update badge. Clicking applies the selected theme instantly via Gtk::StyleContext::add_provider_for_display() at GTK_STYLE_PROVIDER_PRIORITY_USER — no restart required.

  • Active highlight — the button for the currently active theme receives the CSS class theme-btn-active (full opacity); the inactive button is dimmed (opacity: 0.5).

  • Preference saved — theme choice written to ~/.config/BastionGuard/theme.conf on every click. File contains a single word: light or dark.


Startup restore (StyleProvider.cpp)

  • read_saved_theme() — new helper function reads ~/.config/BastionGuard/theme.conf before the main window is shown. Returns "dark" or "light" (default if file absent or value unrecognised).

  • load_style() updated — selects BastionGuard-dark.css or BastionGuard.css based on the saved theme. Search order: data/ (local build), then DATA_DIR (installed path). Falls back to light theme if the requested CSS file is not found — no crash, no blank window.

  • First launch — if theme.conf does not exist, light theme is used. No migration required from previous installations.


Settings → Options (SettingsPage.cpp)

  • Theme selectorGtk::ComboBoxText with two entries: ☀ Chiaro (Light) and 🌙 Scuro (Dark). Shows the current saved theme on open.

  • Preview label — describes the selected theme (background colour, text colour) and updates live as the user changes the selection.

  • "Applica tema" button — loads the CSS file and applies it immediately via add_provider_for_display. Shows a confirmation dialog on success; shows an error dialog if the CSS file is not found. Saves preference to theme.conf.


Bug fixes

Component Bug Fix
StyleProvider.cpp App always started in light mode regardless of saved preference load_style() now reads theme.conf before selecting the CSS file
DashboardPage.cpp Theme buttons had no visual feedback for current selection theme-btn-active CSS class added/removed on click and at startup
IdentityLeakPage.cpp Fix dark mode visualization
BankPage.cpp Fix update in silent mode

Compatibility

  • StyleProvider.hpp public API unchanged (load_style() signature identical).
  • BastionGuard.css unchanged — existing installations continue to work without any theme.conf file.
  • BastionGuard-dark.css is a new optional file; absence does not affect the light theme.