# 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 ``, 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 logo** β€” `load_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 `` (PNG logo) and `` (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=` 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 write** β€” `secrets.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` 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 selector** β€” `Gtk::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. ---