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 justhttps://host/. Fixes the PayPal checkout link case that was opening onlypaypal.comand 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_closefor parallel requests arriving before the firstdo_interceptcompletes. -
block_active_session()— new variant ofblock_secure_requestused in Case 2: serves the TLS block page with no callback and nolaunch_sandbox, eliminating the root cause of the tab loop. -
launch_sandboxis now blocking — removed the&from the shell command.std::systemnow waits for the child process to terminate, enablingsession_closeon return. -
session_close(root)— new function that removes the session fromg_sessionsimmediately when the sandbox is closed.SESSION_TTLraised 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 preventLocalWarningServerfrom starting inside them, eliminating interference with internal redirects (e.g. the vendor's return URL). -
SandboxApiServer— new HTTP server on127.0.0.1:3131handlingGET /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— replacesTunnelSessionfor 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
autocompleteattributes (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
- HTML5
-
inject_into_response()— inserts the JS script before</head>, updatesContent-Length, removesTransfer-Encoding: chunkedif 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:
should_inspect_for_card_forms(host)— MITM only on known e-commerce hosts (~80 sites: Amazon, eBay, Zalando, etc.); CDN and trackers excluded.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.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.
LocalWarningServerno longer interferes thanks toBG_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 ofSimpleHandlerthat generates a block page asdata:text/html,…directly in CEF, with no dependency onLocalWarningServer. -
Conditional
start_local_warning_server()— inSecureBrowser::open(), theLocalWarningServeris started only if theBG_SANDBOXenvironment 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.pngand 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
.shieldCSS — supports both<img>(PNG logo) and<span>(emoji fallback): fixed size72×72px,object-fit:contain, bluedrop-shadowfor 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.hppis a new optional header; targets that do not include it are unaffected.BG_SANDBOX_API_PORT(default3131) can be overridden at compile time via-DBG_SANDBOX_API_PORT=<port>inCMakeLists.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.encis written viarename()on a.tmpfile. 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.encon the next unlock.
Performance (PasswordVault.cpp)
-
In-memory RAM cache — on unlock, all secrets are loaded from
secrets.encinto anunordered_map<id, plaintext>protected by astd::mutex. SubsequentgetSecretAsync()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 tosecrets.encwith a single encrypt + write operation. -
getSecretAsync()— three-level lookup:- Cache hit → instant response via
Glib::signal_idle - Warm-up in progress → 50 ms polling via
Glib::signal_timeout(max 5 s) - Cache ready but id not found → fallback for edge cases
- Cache hit → instant response via
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\nline 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::Dispatcherfrom 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.csvfilter.
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.
- 🟢 Green (
-
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()atGTK_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.confon every click. File contains a single word:lightordark.
Startup restore (StyleProvider.cpp)
-
read_saved_theme()— new helper function reads~/.config/BastionGuard/theme.confbefore the main window is shown. Returns"dark"or"light"(default if file absent or value unrecognised). -
load_style()updated — selectsBastionGuard-dark.cssorBastionGuard.cssbased on the saved theme. Search order:data/(local build), thenDATA_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.confdoes not exist, light theme is used. No migration required from previous installations.
Settings → Options (SettingsPage.cpp)
-
Theme selector —
Gtk::ComboBoxTextwith 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 totheme.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.hpppublic API unchanged (load_style()signature identical).BastionGuard.cssunchanged — existing installations continue to work without anytheme.conffile.BastionGuard-dark.cssis a new optional file; absence does not affect the light theme.