Skip to content
API Documentation
BastionGuard™
SecureBrowser.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#include "SecureBrowser.hpp"
22#include "DistroUtils.hpp"
23#include "SettingsStore.hpp"
25
26#include <include/cef_app.h>
27#include <include/cef_browser.h>
28#include <include/cef_client.h>
29#include <include/cef_command_line.h>
30#include <include/cef_request_handler.h>
31#include <include/cef_ssl_info.h>
32#include <include/views/cef_browser_view.h>
33#include <include/views/cef_browser_view_delegate.h>
34#include <include/views/cef_window.h>
35#include <include/views/cef_window_delegate.h>
36#include <include/wrapper/cef_helpers.h>
37#include <include/cef_callback.h>
38#include <glibmm/miscutils.h>
39
40#include <atomic>
41#include <algorithm>
42#include <chrono>
43#include <cstdlib>
44#include <cstring>
45#include <filesystem>
46#include <fstream>
47#include <iostream>
48#include <sstream>
49#include <string>
50#include <thread>
51#include <vector>
52#include <unordered_set>
53
54#include <fcntl.h>
55#include <sys/file.h>
56#include <sys/stat.h>
57#include <unistd.h>
58#include <glibmm/i18n.h>
59#include <nlohmann/json.hpp>
60
61#define BASTIONGUARD_LOCAL_WARNING_IP "127.0.0.2"
62#define BASTIONGUARD_WARNING_DEFAULT_HTTP 81
63
64namespace fs = std::filesystem;
65
66static std::unique_ptr<LocalWarningServer> g_warning_server;
67static std::atomic<bool> g_cef_started{false};
68
69static bool is_payment_domain(const std::string& host);
70
71static void clear_proxy_env() {
72 unsetenv("http_proxy");
73 unsetenv("https_proxy");
74 unsetenv("ftp_proxy");
75 unsetenv("all_proxy");
76 unsetenv("HTTP_PROXY");
77 unsetenv("HTTPS_PROXY");
78 unsetenv("FTP_PROXY");
79 unsetenv("ALL_PROXY");
80 unsetenv("no_proxy");
81 unsetenv("NO_PROXY");
82}
83
85 if (!g_warning_server) {
86 g_warning_server = std::make_unique<LocalWarningServer>(
88 444,
90 "",
91 "",
92 ""
93 );
94 g_warning_server->start();
95 }
96}
97
99public:
100 explicit InstanceLock(const char* path) : fd_(-1), path_(path) {
101 fd_ = ::open(path, O_CREAT | O_RDWR, 0600);
102 if (fd_ >= 0) {
103 if (flock(fd_, LOCK_EX | LOCK_NB) != 0) {
104 ::close(fd_);
105 fd_ = -1;
106 } else {
107 ftruncate(fd_, 0);
108 std::string pid = std::to_string(getpid());
109 ::write(fd_, pid.c_str(), pid.size());
110 ::fsync(fd_);
111 }
112 }
113 }
114
115 bool acquired() const { return fd_ >= 0; }
116
118 if (fd_ >= 0) {
119 flock(fd_, LOCK_UN);
120 ::close(fd_);
121 }
122 }
123
124private:
125 int fd_;
126 std::string path_;
127};
128
129static std::string to_lower(std::string s) {
130 std::transform(s.begin(), s.end(), s.begin(),
131 [](unsigned char c){ return static_cast<char>(std::tolower(c)); });
132 return s;
133}
134
135static std::string clean_domain(std::string d) {
136 d = to_lower(d);
137 if (!d.empty() && d[0] == '.') d.erase(0, 1);
138 if (d.rfind("www.", 0) == 0) d.erase(0, 4);
139 auto p = d.find(':');
140 if (p != std::string::npos) d.erase(p);
141 return d;
142}
143
144static std::string host_from_url(const std::string& url) {
145 std::string s = url;
146 auto pos = s.find("://");
147 if (pos != std::string::npos) s.erase(0, pos + 3);
148 pos = s.find('/');
149 if (pos != std::string::npos) s.erase(pos);
150 pos = s.find('@');
151 if (pos != std::string::npos) s = s.substr(pos + 1);
152 return clean_domain(s);
153}
154
155static void load_list_file(const std::string& path, std::vector<std::string>& out) {
156 if (!fs::exists(path)) {
157 std::cerr << _("[SecureBrowser] ⚠ File non trovato: ") << path << "\n";
158 return;
159 }
160
161 try {
162 std::ifstream f(path, std::ios::binary);
163 if (!f.good()) {
164 std::cerr << _("[SecureBrowser] ⚠ Impossibile aprire: ") << path << "\n";
165 return;
166 }
167
168 std::string data((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
169 data.erase(std::remove(data.begin(), data.end(), '\0'), data.end());
170
171 nlohmann::json j = nlohmann::json::parse(data, nullptr, true, true);
172
173 auto push_domain = [&](const std::string& d_raw) {
174 std::string d = clean_domain(d_raw);
175 if (!d.empty()) out.push_back(d);
176 };
177
178 if (j.is_object()) {
179 if (j.contains("list") && j["list"].is_array()) {
180 for (const auto& v : j["list"]) {
181 if (v.is_string()) push_domain(v.get<std::string>());
182 }
183 } else if (j.contains("domains") && j["domains"].is_array()) {
184 for (const auto& v : j["domains"]) {
185 if (v.is_string()) push_domain(v.get<std::string>());
186 }
187 } else if (j.contains("whitelist") && j["whitelist"].is_array()) {
188 for (const auto& v : j["whitelist"]) {
189 if (v.is_string()) push_domain(v.get<std::string>());
190 }
191 }
192 } else if (j.is_array()) {
193 for (const auto& v : j) {
194 if (v.is_string()) push_domain(v.get<std::string>());
195 }
196 }
197
198 std::cerr << _("[SecureBrowser] ✅ Caricati ") << out.size()
199 << _(" domini trusted da ") << path << "\n";
200 } catch (const std::exception& e) {
201 std::cerr << Glib::ustring::compose(
202 _("[SecureBrowser] ❌ Errore parsing JSON (%1): %2"),
203 path,
204 e.what()
205 ) << "\n";
206 }
207}
208
209static bool is_trusted_domain(const std::string& host) {
210 static std::unordered_set<std::string> trusted_domains;
211 static bool initialized = false;
212
213 if (!initialized) {
214 std::vector<std::string> tmp;
215 std::cerr << _("[SecureBrowser] 🔍 Caricamento domini trusted...\n");
216 load_list_file("/usr/share/BastionGuard/data/bank/banks.json", tmp);
217
218 const char* home = std::getenv("HOME");
219 if (home) {
220 load_list_file(std::string(home) + "/.local/share/BastionGuard/banks.json", tmp);
221 load_list_file(std::string(home) + "/.config/BastionGuard/whitelist.json", tmp);
222 }
223
224 for (auto& d : tmp) {
225 trusted_domains.insert(clean_domain(d));
226 }
227
228 std::cerr << _("[SecureBrowser] ✅ Cache inizializzata: ")
229 << trusted_domains.size() << _(" domini trusted.\n");
230
231 if (trusted_domains.count("intesasanpaolo.com"))
232 std::cerr << _("[SecureBrowser] 🔎 Verifica: 'intesasanpaolo.com' presente in cache.\n");
233 else
234 std::cerr << _("[SecureBrowser] 🔎 Verifica: 'intesasanpaolo.com' NON presente in cache.\n");
235
236 initialized = true;
237 }
238
239 const std::string h = clean_domain(host);
240
241 if (trusted_domains.count(h)) {
242 std::cerr << _("[SecureBrowser] 🟢 Trusted (match diretto): ") << h << "\n";
243 return true;
244 }
245
246 auto dot = h.find('.');
247 while (dot != std::string::npos) {
248 std::string parent = h.substr(dot + 1);
249 if (trusted_domains.count(parent)) {
250 std::cerr << Glib::ustring::compose(
251 _("[SecureBrowser] 🟢 Trusted (match parent '%1'): %2"),
252 parent,
253 h
254 ) << "\n";
255 return true;
256 }
257 dot = h.find('.', dot + 1);
258 }
259
260 std::cerr << _("[SecureBrowser] 🔴 NON trusted: ") << h << "\n";
261 return false;
262}
263
264class SimpleHandler : public CefClient,
265public CefLifeSpanHandler,
266public CefDisplayHandler,
267public CefRequestHandler {
268public:
269 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
270 CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
271 CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
272
273 void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
274 CEF_REQUIRE_UI_THREAD();
275 browsers_.push_back(browser);
276 std::cout << _("[SecureBrowser] 🌐 Browser creato.\n");
277 }
278
279 void OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString&) override {
280 CEF_REQUIRE_UI_THREAD();
281 inject_watermark(browser);
282 }
283
284 void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
285 CEF_REQUIRE_UI_THREAD();
286 browsers_.erase(
287 std::remove_if(
288 browsers_.begin(),
289 browsers_.end(),
290 [&](auto& b){ return b->IsSame(browser); }
291 ),
292 browsers_.end()
293 );
294
295 if (browsers_.empty()) {
296 std::cout << _("[SecureBrowser] 🔒 Tutte le finestre chiuse — uscita loop.\n");
297 CefQuitMessageLoop();
298 }
299 }
300
301 bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
302 CefRefPtr<CefFrame> frame,
303 CefRefPtr<CefRequest> request,
304 bool,
305 bool) override {
306 CEF_REQUIRE_UI_THREAD();
307
308 const std::string url = request->GetURL();
309 const std::string host = host_from_url(url);
310
311 std::cout << _("[SecureBrowser] 🌍 URL richiesto: ") << url << "\n";
312 std::cout << _("[SecureBrowser] 🧩 Host estratto: ") << host << "\n";
313
314 if (host == "127.0.0.2" || host == "127.0.0.1" || host == "localhost") {
315 std::cout << _("[SecureBrowser] ✅ Dominio locale consentito.\n");
316 return false;
317 }
318
319 const bool is_main_frame = frame && frame->IsMain();
320
321 const bool bank_ok = is_trusted_domain(host);
322 const bool pay_ok = is_payment_domain(host);
323 const bool https_url = url.rfind("https://", 0) == 0;
324
325 // A previously authorized page never grants permission to
326 // navigate the main frame to a different, untrusted origin.
327 if (is_main_frame) {
328 if (https_url && (bank_ok || pay_ok)) {
329 std::cout << _("[SecureBrowser] 🟢 Navigazione attendibile: ") << host << "\n";
330 return false;
331 }
332 } else if (https_url) {
333 // Third-party HTTPS subframes are needed by some bank
334 // sign-in/payment flows; main-frame trust is independent.
335 return false;
336 }
337
338 std::cout << _("[SecureBrowser] 🚫 Navigazione bloccata verso dominio non trusted: ") << host << "\n";
339 load_block_page_inline(browser, host);
340 return true;
341 }
342
343 bool OnCertificateError(CefRefPtr<CefBrowser> browser,
344 cef_errorcode_t cert_error,
345 const CefString& request_url,
346 CefRefPtr<CefSSLInfo>,
347 CefRefPtr<CefCallback> callback) override {
348 CEF_REQUIRE_UI_THREAD();
349
350 const std::string url = request_url;
351 const std::string host = host_from_url(url);
352
353 std::cout << _("[SecureBrowser] ⛔ Cert error (") << cert_error
354 << _(") per dominio: ") << host << "\n";
355
356 // A trusted-domain allowlist must never override TLS
357 // certificate validation. Fail closed for every host.
358 if (browser && browser->GetMainFrame())
359 load_block_page_inline(browser, host);
360 if (callback) callback->Cancel();
361 return true;
362 }
363
364private:
365 static void load_block_page_inline(CefRefPtr<CefBrowser> browser,
366 const std::string& host) {
367 std::string safe_host;
368 for (char c : host) {
369 if (c == '<') safe_host += "&lt;";
370 else if (c == '>') safe_host += "&gt;";
371 else if (c == '&') safe_host += "&amp;";
372 else if (c == '"') safe_host += "&quot;";
373 else safe_host += c;
374 }
375
376 const std::string html =
377 "<!DOCTYPE html><html><head><meta charset='UTF-8'>"
378 "<style>"
379 "body{margin:0;height:100vh;display:flex;align-items:center;"
380 "justify-content:center;background:#1a1a2e;font-family:system-ui,sans-serif;}"
381 ".card{background:#16213e;border:1px solid #0f3460;border-radius:12px;"
382 "padding:40px 36px;max-width:460px;text-align:center;color:#e0e0e0;}"
383 "h1{color:#e94560;font-size:1.3rem;margin:0 0 16px;}"
384 "p{color:#a0a0b0;font-size:.9rem;line-height:1.6;margin:0 0 24px;}"
385 ".domain{font-family:monospace;color:#4fc3f7;background:#0d1b2a;"
386 "padding:4px 10px;border-radius:4px;}"
387 "button{background:#0f3460;color:#fff;border:none;border-radius:8px;"
388 "padding:10px 24px;font-size:.9rem;cursor:pointer;}"
389 "button:hover{background:#1a4a80;}"
390 "</style></head><body>"
391 "<div class='card'>"
392 "<h1>🛡️ BastionGuard — Navigazione bloccata</h1>"
393 "<p>Il dominio <span class='domain'>" + safe_host + "</span> "
394 "non è accessibile dalla sessione sicura.<br><br>"
395 "Chiudi questa finestra e aprilo nel tuo browser normale.</p>"
396 "<button onclick='window.close()'>Chiudi</button>"
397 "</div></body></html>";
398
399 std::string encoded;
400 for (unsigned char c : html) {
401 if (std::isalnum(c) || c == '-' || c == '_' || c == '.' ||
402 c == '!' || c == '~' || c == '*' || c == '\'' ||
403 c == '(' || c == ')') {
404 encoded += static_cast<char>(c);
405 } else {
406 char buf[4];
407 std::snprintf(buf, sizeof(buf), "%%%02X", c);
408 encoded += buf;
409 }
410 }
411
412 if (browser && browser->GetMainFrame())
413 browser->GetMainFrame()->LoadURL("data:text/html," + encoded);
414 }
415
416 void inject_watermark(CefRefPtr<CefBrowser> browser) {
417 static const char kJS[] = R"JS(
418 (function(){
419 if (document.getElementById('BastionGuard_wm')) return;
420 const wm = document.createElement('div');
421 wm.id = 'BastionGuard_wm';
422 wm.textContent = '🟢 BastionGuard Secure Browser';
423 Object.assign(wm.style, {
424 position: 'fixed',
425 bottom: '14px',
426 right: '16px',
427 background: 'rgba(46,125,50,0.9)',
428 color: '#fff',
429 fontFamily: 'system-ui, sans-serif',
430 fontWeight: '600',
431 fontSize: '13px',
432 padding: '6px 12px',
433 borderRadius: '10px',
434 zIndex: '2147483647',
435 pointerEvents: 'none',
436 boxShadow: '0 2px 6px rgba(0,0,0,0.3)'
437 });
438 if (document.body) document.body.appendChild(wm);
439 })();
440 )JS";
441
442 CefRefPtr<CefFrame> frame = browser->GetMainFrame();
443 if (frame && frame->IsValid())
444 frame->ExecuteJavaScript(kJS, frame->GetURL(), 0);
445 }
446
447 std::vector<CefRefPtr<CefBrowser>> browsers_;
449};
450
451// -----------------------------------------------------------------------------
452// Popup CEF Views
453//
454// Il browser principale usa CefBrowserView. Senza un delegate Views esplicito i
455// popup creati da window.open()/target=_blank possono finire nella finestra CEF
456// predefinita invece che in una finestra BastionGuard. Questo e' particolarmente
457// visibile nei flussi OTP/3-D Secure.
458//
459// Manteniamo lo stesso CefClient/SimpleHandler e creiamo SOLO il contenitore
460// grafico del popup. Nessun cambio a routing, proxy, sandbox o policy domini.
461// -----------------------------------------------------------------------------
462class SecurePopupWindowDelegate : public CefWindowDelegate {
463public:
464 explicit SecurePopupWindowDelegate(CefRefPtr<CefBrowserView> view)
465 : view_(view) {}
466
467 void OnWindowCreated(CefRefPtr<CefWindow> window) override {
468 CEF_REQUIRE_UI_THREAD();
469 if (!view_) return;
470
471 window->AddChildView(view_);
472 window->SetBounds(CefRect(160, 120, 1000, 720));
473 view_->SetBounds(CefRect(0, 0, 1000, 720));
474 window->SetTitle("BastionGuard Secure Browser");
475 window->Show();
476 view_->RequestFocus();
477
478 std::cout << _("[SecureBrowser] 🪟 Popup CEF mostrato nella sessione sicura.\n");
479 }
480
481 void OnWindowDestroyed(CefRefPtr<CefWindow>) override {
482 CEF_REQUIRE_UI_THREAD();
483 // Non uscire dal message loop quando si chiude solo il popup.
484 // SimpleHandler::OnBeforeClose() mantiene il conteggio dei browser.
485 view_ = nullptr;
486 }
487
489
490private:
491 CefRefPtr<CefBrowserView> view_;
492};
493
494class SecureBrowserViewDelegate : public CefBrowserViewDelegate {
495public:
496 CefRefPtr<CefBrowserViewDelegate> GetDelegateForPopupBrowserView(
497 CefRefPtr<CefBrowserView>,
498 const CefBrowserSettings&,
499 CefRefPtr<CefClient>,
500 bool) override {
501 // Riusa lo stesso delegate anche per eventuali popup annidati.
502 return this;
503 }
504
506 CefRefPtr<CefBrowserView>,
507 CefRefPtr<CefBrowserView> popup_browser_view,
508 bool is_devtools) override {
509 CEF_REQUIRE_UI_THREAD();
510
511 if (is_devtools || !popup_browser_view)
512 return false;
513
514 CefWindow::CreateTopLevelWindow(
515 new SecurePopupWindowDelegate(popup_browser_view));
516
517 // La finestra e' stata creata da BastionGuard: evita la finestra popup
518 // predefinita di CEF/Chromium.
519 return true;
520 }
521
523};
524
525class ClamApp : public CefApp, public CefBrowserProcessHandler {
526public:
527 CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }
528
529 void OnBeforeCommandLineProcessing(const CefString&, CefRefPtr<CefCommandLine> cmd) override {
530 // Il Secure Browser usa un profilo CEF dedicato e persistente. Non
531 // forzare --incognito nel processo bancario/payment: gli auth cookie
532 // di sessione devono poter sopravvivere alla chiusura della finestra,
533 // altrimenti ogni riapertura appare come un nuovo device/sessione ai
534 // sistemi SCA/3DS/anti-abuso. La finestra notify resta invece
535 // incognito perché BG_CEF_PERSIST_SESSION non viene impostato lì.
536 if (std::getenv("BG_CEF_PERSIST_SESSION"))
537 cmd->AppendSwitch("persist-session-cookies");
538 else
539 cmd->AppendSwitch("incognito");
540
541 cmd->AppendSwitch("disable-extensions");
542 cmd->AppendSwitch("disable-sync");
543 cmd->AppendSwitch("disable-component-update");
544 cmd->AppendSwitch("disable-print-preview");
545
546 // Alcuni flussi bancari/3-D Secure aprono la finestra di conferma dopo
547 // una callback asincrona e Chromium puo' classificarla come popup non
548 // direttamente associato al click. Nel processo SecureBrowser i popup
549 // devono restare consentiti e vengono comunque contenuti nel CEF Views
550 // gestito sopra.
551 cmd->AppendSwitch("disable-popup-blocking");
552
553 cmd->AppendSwitchWithValue("disable-features", "TranslateUI");
554 cmd->AppendSwitchWithValue("ssl-certificates-file", "/etc/ssl/certs/ca-certificates.crt");
555
556 cmd->AppendSwitchWithValue("dns-over-https-mode", "automatic");
557 cmd->AppendSwitchWithValue(
558 "dns-over-https-servers",
559 "https://cloudflare-dns.com/dns-query,https://dns.google/dns-query"
560 );
561
562 cmd->AppendSwitch("no-proxy-server");
563 cmd->AppendSwitchWithValue("proxy-server", "direct://");
564 cmd->AppendSwitch("proxy-bypass-list=*");
565
566 cmd->AppendSwitch("enable-gpu");
567 cmd->AppendSwitch("disable-gpu-compositing");
568 cmd->AppendSwitch("use-gl=desktop");
569
570 // -----------------------------------------------------------------
571 // Selezione backend Ozone (Wayland o X11).
572 //
573 // L'ordine di priorità è (high → low):
574 // 1) BastionGuard_CEF_BACKEND = wayland|x11 (override interno usato
575 // dal fallback in SecureBrowser::open per ritentare con X11)
576 // 2) OZONE_PLATFORM = wayland|x11 (env standard Chromium)
577 // 3) XDG_SESSION_TYPE = wayland|x11 (logind)
578 // 4) WAYLAND_DISPLAY presente → wayland, altrimenti DISPLAY → x11
579 // Se nulla di tutto questo è presente, default = x11 (più resiliente).
580 // -----------------------------------------------------------------
581 auto eq = [](const char* a, const char* b) -> bool {
582 return a && b && std::strcmp(a, b) == 0;
583 };
584
585 const char* internal_be = std::getenv("BastionGuard_CEF_BACKEND");
586 const char* ozone_be = std::getenv("OZONE_PLATFORM");
587 const char* sess_type = std::getenv("XDG_SESSION_TYPE");
588 const char* wl_disp = std::getenv("WAYLAND_DISPLAY");
589 const char* x_disp = std::getenv("DISPLAY");
590
591 bool want_wayland;
592 const char* picked_by;
593
594 if (eq(internal_be, "wayland")) { want_wayland = true; picked_by = "BastionGuard_CEF_BACKEND"; }
595 else if (eq(internal_be, "x11")) { want_wayland = false; picked_by = "BastionGuard_CEF_BACKEND"; }
596 else if (eq(ozone_be, "wayland")) { want_wayland = true; picked_by = "OZONE_PLATFORM"; }
597 else if (eq(ozone_be, "x11")) { want_wayland = false; picked_by = "OZONE_PLATFORM"; }
598 else if (eq(sess_type, "wayland")) { want_wayland = true; picked_by = "XDG_SESSION_TYPE"; }
599 else if (eq(sess_type, "x11")) { want_wayland = false; picked_by = "XDG_SESSION_TYPE"; }
600 else if (wl_disp && *wl_disp) { want_wayland = true; picked_by = "WAYLAND_DISPLAY"; }
601 else if (x_disp && *x_disp) { want_wayland = false; picked_by = "DISPLAY"; }
602 else { want_wayland = false; picked_by = "default(x11)"; }
603
604 std::cerr << "[SecureBrowser] Ozone backend: "
605 << (want_wayland ? "wayland" : "x11")
606 << " (via " << picked_by << ")\n";
607
608 cmd->AppendSwitchWithValue(
609 "enable-features",
610 "UseOzonePlatform,VaapiVideoDecoder,UseSkiaRenderer,DnsOverHttps"
611 );
612
613 if (want_wayland) {
614 cmd->AppendSwitchWithValue("ozone-platform", "wayland");
615 cmd->AppendSwitch("enable-webrtc-pipewire-capturer");
616 } else {
617 cmd->AppendSwitchWithValue("ozone-platform", "x11");
618 // Su X11 puro evita warning di Chromium su Wayland mancante
619 cmd->AppendSwitchWithValue("ozone-platform-hint", "x11");
620 }
621
622 cmd->AppendSwitch("no-default-browser-check");
623 cmd->AppendSwitch("no-first-run");
624 cmd->AppendSwitch("disable-crash-reporter");
625 cmd->AppendSwitch("disable-background-networking");
626 cmd->AppendSwitch("disable-breakpad");
627 }
628
630};
631
632CefRefPtr<CefApp> SecureBrowser::createApp() {
633 return new ClamApp();
634}
635
636bool SecureBrowser::open(const std::string& url, int argc, char** argv) {
638 std::cerr << _("[SecureBrowser] CEF disabilitato su questa build o distribuzione.\n");
639 return false;
640 }
641
642 if (!std::getenv("BG_SANDBOX"))
644
645 bool expected = false;
646 if (!g_cef_started.compare_exchange_strong(expected, true)) {
647 std::cerr << _("[SecureBrowser] ⚠️ Richiesta duplicata nello stesso processo.\n");
648 return false;
649 }
650
651 InstanceLock lock("/tmp/BastionGuard-bankopener.lock");
652 if (!lock.acquired()) {
653 std::cerr << _("[SecureBrowser] ℹ️ Istanza già attiva. Rifiuto nuovo avvio.\n");
654 g_cef_started = false;
655 return false;
656 }
657
658 auto fail = [&](const std::string& msg) -> bool {
659 std::cerr << msg << "\n";
660 g_cef_started = false;
661 return false;
662 };
663
664 std::cout << _("[SecureBrowser] 🧩 Avvio sandbox Blink/CEF per URL: ") << url << std::endl;
665
667
668 setenv("SSL_CERT_DIR", "/etc/ssl/certs", 1);
669 setenv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt", 1);
670 setenv("NSS_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt", 1);
671 setenv("NSS_CERT_DIR", "/etc/ssl/certs", 1);
672
673 const std::string cef_dir = "/usr/share/BastionGuard/cef";
674 const std::string cache_dir = Glib::get_home_dir() + "/.cache/BastionGuard/cef_sandbox";
675 std::error_code ec;
676 fs::create_directories(cache_dir, ec);
677 if (ec) {
678 std::cerr << _("[SecureBrowser] ⚠️ Creazione cache_dir fallita: ") << ec.message() << "\n";
679 }
680
681 // Profilo CEF dedicato persistente: non importiamo cookie dal browser
682 // esterno (Firefox/Chrome), ma manteniamo quelli creati dalla sessione
683 // sicura. Questo evita di mischiare token legati a fingerprint diversi.
684 setenv("BG_CEF_PERSIST_SESSION", "1", 1);
685
686 CefMainArgs args(argc, argv);
687 CefRefPtr<CefApp> app = createApp();
688
689 auto try_init_cef = [&](const char* backend) -> bool {
690 if (backend) setenv("BastionGuard_CEF_BACKEND", backend, 1);
691
692 CefSettings settings;
693 settings.no_sandbox = true;
694 settings.windowless_rendering_enabled = false;
695 settings.log_severity = LOGSEVERITY_WARNING;
696 settings.background_color = CefColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
697
698 CefString(&settings.resources_dir_path) = cef_dir;
699 CefString(&settings.locales_dir_path) = cef_dir + "/locales";
700 CefString(&settings.cache_path) = cache_dir;
701
702 // Il tuo SDK CEF espone persist_session_cookies (a differenza di
703 // persist_user_preferences, che nella build in uso non esiste).
704 // Con cache_path non vuoto conserva anche i cookie senza Expires/Max-Age.
705 settings.persist_session_cookies = true;
706
707 bool init_ok = CefInitialize(args, settings, app, nullptr);
708 if (!init_ok) {
709 std::cerr << _("[SecureBrowser] ❌ CefInitialize returned false for backend: ")
710 << (backend ? backend : "auto") << "\n";
711 }
712 return init_ok;
713 };
714
715 // -------------------------------------------------------------------------
716 // Scelta backend prima di CefInitialize.
717 //
718 // CEF chiama OnBeforeCommandLineProcessing UNA VOLTA SOLA, prima del primo
719 // CefInitialize, e CefInitialize non si può ri-eseguire dentro lo stesso
720 // processo. Quindi il backend va deciso QUI, e l'eventuale fallback X11
721 // va impostato via env (BastionGuard_CEF_BACKEND/OZONE_PLATFORM) prima di
722 // toccare CefInitialize. Se CefInitialize fallisce, il processo deve
723 // ri-execvp se stesso impostando l'env diverso.
724 // -------------------------------------------------------------------------
725 auto pick_backend = []() -> const char* {
726 if (const char* b = std::getenv("BastionGuard_CEF_BACKEND")) {
727 if (std::strcmp(b, "wayland") == 0 || std::strcmp(b, "x11") == 0)
728 return b;
729 }
730 if (const char* o = std::getenv("OZONE_PLATFORM")) {
731 if (std::strcmp(o, "wayland") == 0 || std::strcmp(o, "x11") == 0)
732 return o;
733 }
734 if (const char* s = std::getenv("XDG_SESSION_TYPE")) {
735 if (std::strcmp(s, "wayland") == 0) return "wayland";
736 if (std::strcmp(s, "x11") == 0) return "x11";
737 }
738 if (std::getenv("WAYLAND_DISPLAY")) return "wayland";
739 if (std::getenv("DISPLAY")) return "x11";
740 return "x11"; // default safe
741 };
742
743 const char* backend_pref = pick_backend();
744 std::cerr << _("[SecureBrowser] backend preferito: ") << backend_pref << "\n";
745
746 bool ok = try_init_cef(backend_pref);
747
748 // Fallback: se Wayland fallisce e DISPLAY è disponibile, ri-execvp con X11.
749 // (CefInitialize non si può richiamare nello stesso processo.)
750 if (!ok && std::strcmp(backend_pref, "wayland") == 0 &&
751 std::getenv("DISPLAY") && !std::getenv("BG_CEF_X11_FALLBACK")) {
752 std::cerr << _("[SecureBrowser] ⚠️ CEF Wayland fallito → re-exec con X11.\n");
753 setenv("BG_CEF_X11_FALLBACK", "1", 1);
754 setenv("BastionGuard_CEF_BACKEND", "x11", 1);
755 setenv("OZONE_PLATFORM", "x11", 1);
756 setenv("GDK_BACKEND", "x11", 1);
757 unsetenv("WAYLAND_DISPLAY");
758 // execvp con argv originale: re-entry sicuro, niente stato CEF a metà.
759 if (argv && argv[0]) {
760 std::vector<char*> reargv;
761 for (int i = 0; i < argc; ++i) reargv.push_back(argv[i]);
762 reargv.push_back(nullptr);
763 execvp(argv[0], reargv.data());
764 std::perror("execvp(x11 fallback)");
765 }
766 }
767
768 if (!ok) {
769 return fail(_("[SecureBrowser] ❌ CefInitialize fallito definitivamente."));
770 }
771
772 CefRefPtr<SimpleHandler> handler = new SimpleHandler();
773 CefRefPtr<SecureBrowserViewDelegate> view_delegate =
775
776 CefBrowserSettings bset;
777 bset.javascript = STATE_ENABLED;
778 bset.webgl = STATE_ENABLED;
779
780 CefRefPtr<CefBrowserView> mainView =
781 CefBrowserView::CreateBrowserView(
782 handler, url, bset, nullptr, nullptr, view_delegate);
783
784 if (!mainView) {
785 CefShutdown();
786 return fail(_("[SecureBrowser] ❌ CreateBrowserView() ha restituito nullptr."));
787 }
788
789 class WindowDelegate : public CefWindowDelegate {
790 public:
791 explicit WindowDelegate(CefRefPtr<CefBrowserView> main) : mainView_(main) {}
792
793 void OnWindowCreated(CefRefPtr<CefWindow> window) override {
794 window->AddChildView(mainView_);
795 window->SetBounds(CefRect(100, 100, 1200, 800));
796 mainView_->SetBounds(CefRect(0, 0, 1200, 800));
797 window->SetTitle("BastionGuard Secure Browser");
798 window->Show();
799 }
800
801 void OnWindowDestroyed(CefRefPtr<CefWindow>) override {
802 CefQuitMessageLoop();
803 }
804
805 IMPLEMENT_REFCOUNTING(WindowDelegate);
806
807 private:
808 CefRefPtr<CefBrowserView> mainView_;
809 };
810
811 CefWindow::CreateTopLevelWindow(new WindowDelegate(mainView));
812
813 std::cout << _("[SecureBrowser] ✅ Finestra sandbox creata, avvio message loop.\n");
814
815 CefRunMessageLoop();
816 CefShutdown();
817
818 g_cef_started = false;
819 std::cout << _("[SecureBrowser] ✅ Sandbox chiusa correttamente.\n");
820 return true;
821}
822
823static bool is_payment_domain(const std::string& host) {
825 return false;
826
827 // Provider di pagamento "navigabili" riconosciuti SEMPRE: senza questo,
828 // il filtro di navigazione del SecureBrowser bloccherebbe Stripe/PayPal/
829 // ecc. quando non sono in payments.json, lasciando una pagina vuota nella
830 // sandbox. NON includere endpoint tecnici (js./api./hooks./cdn.): quelli
831 // sono comunque consentiti come subframe in sessione trusted.
832 {
833 static const std::unordered_set<std::string> builtin = {
834 "stripe.com", "paypal.com", "paypalobjects.com", "adyen.com",
835 "checkout.com", "klarna.com", "worldpay.com", "braintreegateway.com",
836 "nexi.it", "nexigroup.com", "sisalpay.it", "satispay.com",
837 "sumup.com", "square.com", "squareup.com", "mollie.com",
838 "gocardless.com", "wise.com", "revolut.com", "skrill.com",
839 "neteller.com", "paysafecard.com", "amazonpay.com",
840 "wallet.google.com", "pay.google.com",
841 };
842 const std::string hb = clean_domain(host);
843 if (builtin.count(hb)) return true;
844 auto d = hb.find('.');
845 while (d != std::string::npos) {
846 if (builtin.count(hb.substr(d + 1))) return true;
847 d = hb.find('.', d + 1);
848 }
849 }
850
851 static std::unordered_set<std::string> payment_domains;
852 static bool initialized = false;
853
854 if (!initialized) {
855 std::vector<std::string> tmp;
856 std::cerr << _("[SecureBrowser] 🔍 Caricamento domini pagamenti...\n");
857
858 load_list_file("/usr/share/BastionGuard/data/payments/payments.json", tmp);
859
860 const char* home = std::getenv("HOME");
861 if (home) {
862 load_list_file(std::string(home) + "/.local/share/BastionGuard/payments/payments.json", tmp);
863 load_list_file(std::string(home) + "/.config/BastionGuard/payments.json", tmp);
864 }
865
866 for (auto& d : tmp) {
867 payment_domains.insert(clean_domain(d));
868 }
869
870 std::cerr << _("[SecureBrowser] ✅ Cache pagamenti inizializzata: ")
871 << payment_domains.size() << _(" domini.\n");
872
873 initialized = true;
874 }
875
876 const std::string h = clean_domain(host);
877
878 if (payment_domains.count(h)) {
879 std::cerr << _("[SecureBrowser] 🟢 Payment trusted (match diretto): ") << h << "\n";
880 return true;
881 }
882
883 auto dot = h.find('.');
884 while (dot != std::string::npos) {
885 std::string parent = h.substr(dot + 1);
886 if (payment_domains.count(parent)) {
887 std::cerr << _("[SecureBrowser] 🟢 Payment trusted (match parent '")
888 << parent << "'): " << h << "\n";
889 return true;
890 }
891 dot = h.find('.', dot + 1);
892 }
893
894 return false;
895}
896
897void SecureBrowser::open_notify(const std::string& url, int argc, char** argv) {
899 std::cerr << _("[SecureBrowser] Notifiche CEF disabilitate su questa build o distribuzione.\n");
900 return;
901 }
902
903 static std::atomic<bool> s_running{false};
904 bool expected = false;
905 if (!s_running.compare_exchange_strong(expected, true)) {
906 std::cerr << _("[SecureBrowser] open_notify: già in esecuzione, ignoro\n");
907 return;
908 }
909
910 InstanceLock lock("/tmp/BastionGuard-notify.lock");
911 if (!lock.acquired()) {
912 std::cerr << _("[SecureBrowser] open_notify: istanza notifica già attiva\n");
913 s_running = false;
914 return;
915 }
916
917 std::cout << _("[SecureBrowser] 🛡️ Apertura finestra notifica: ") << url << "\n";
918
920
921 setenv("SSL_CERT_DIR", "/etc/ssl/certs", 1);
922 setenv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt", 1);
923
924 const std::string cef_dir = "/usr/share/BastionGuard/cef";
925 const std::string cache_dir = Glib::get_home_dir() + "/.cache/BastionGuard/cef_notify";
926 std::error_code ec;
927 fs::create_directories(cache_dir, ec);
928
929 CefMainArgs args(argc ? argc : 0, argv);
930 CefRefPtr<CefApp> app = createApp();
931
932 CefSettings settings;
933 settings.no_sandbox = true;
934 settings.windowless_rendering_enabled = false;
935 settings.log_severity = LOGSEVERITY_DISABLE;
936 settings.background_color = CefColorSetARGB(0xFF, 0x07, 0x09, 0x0F);
937
938 CefString(&settings.resources_dir_path) = cef_dir;
939 CefString(&settings.locales_dir_path) = cef_dir + "/locales";
940 CefString(&settings.cache_path) = cache_dir;
941
942 if (!CefInitialize(args, settings, app, nullptr)) {
943 std::cerr << _("[SecureBrowser] ❌ open_notify: CefInitialize fallito\n");
944 s_running = false;
945 return;
946 }
947
948 class NotifyHandler : public CefClient,
949 public CefLifeSpanHandler,
950 public CefDisplayHandler {
951 public:
952 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
953 CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
954
955 void OnAfterCreated(CefRefPtr<CefBrowser>) override {}
956
957 void OnBeforeClose(CefRefPtr<CefBrowser>) override {
958 CEF_REQUIRE_UI_THREAD();
959 CefQuitMessageLoop();
960 }
961
962 IMPLEMENT_REFCOUNTING(NotifyHandler);
963 };
964
965 CefRefPtr<NotifyHandler> handler = new NotifyHandler();
966 CefBrowserSettings bset;
967 bset.javascript = STATE_ENABLED;
968
969 CefRefPtr<CefBrowserView> view =
970 CefBrowserView::CreateBrowserView(handler, url, bset, nullptr, nullptr, nullptr);
971
972 if (!view) {
973 std::cerr << _("[SecureBrowser] ❌ open_notify: CreateBrowserView fallito\n");
974 CefShutdown();
975 s_running = false;
976 return;
977 }
978
979 class NotifyWindowDelegate : public CefWindowDelegate {
980 public:
981 explicit NotifyWindowDelegate(CefRefPtr<CefBrowserView> v) : view_(v) {}
982
983 void OnWindowCreated(CefRefPtr<CefWindow> w) override {
984 w->AddChildView(view_);
985 w->SetBounds(CefRect(200, 200, 560, 420));
986 view_->SetBounds(CefRect(0, 0, 560, 420));
987 w->SetTitle(_("BastionGuard — Protected Session"));
988 w->Show();
989 }
990
991 void OnWindowDestroyed(CefRefPtr<CefWindow>) override {
992 CefQuitMessageLoop();
993 }
994
995 IMPLEMENT_REFCOUNTING(NotifyWindowDelegate);
996
997 private:
998 CefRefPtr<CefBrowserView> view_;
999 };
1000
1001 CefWindow::CreateTopLevelWindow(new NotifyWindowDelegate(view));
1002 CefRunMessageLoop();
1003 CefShutdown();
1004
1005 s_running = false;
1006 std::cout << _("[SecureBrowser] ✅ Finestra notifica chiusa.\n");
1007}
static void load_list_file(const std::string &path, std::vector< std::string > &out)
static bool is_trusted_domain(const std::string &host)
static std::string clean_domain(std::string d)
#define _(s)
static std::string to_lower(std::string s)
int main(int argc, char *argv[])
static std::unique_ptr< LocalWarningServer > g_warning_server
#define BASTIONGUARD_WARNING_DEFAULT_HTTP
static bool is_trusted_domain(const std::string &host)
static void clear_proxy_env()
void start_local_warning_server()
#define BASTIONGUARD_LOCAL_WARNING_IP
static std::string host_from_url(const std::string &url)
static bool is_payment_domain(const std::string &host)
static std::atomic< bool > g_cef_started
IMPLEMENT_REFCOUNTING(ClamApp)
ClamApp(bool start_minimized)
Definition main.cpp:242
void OnBeforeCommandLineProcessing(const CefString &, CefRefPtr< CefCommandLine > cmd) override
CefRefPtr< CefBrowserProcessHandler > GetBrowserProcessHandler() override
std::string path_
InstanceLock(const char *path)
bool acquired() const
CefRefPtr< CefBrowserViewDelegate > GetDelegateForPopupBrowserView(CefRefPtr< CefBrowserView >, const CefBrowserSettings &, CefRefPtr< CefClient >, bool) override
IMPLEMENT_REFCOUNTING(SecureBrowserViewDelegate)
bool OnPopupBrowserViewCreated(CefRefPtr< CefBrowserView >, CefRefPtr< CefBrowserView > popup_browser_view, bool is_devtools) override
static void open_notify(const std::string &url, int argc, char **argv)
static bool open(const std::string &url, int argc, char **argv)
static CefRefPtr< CefApp > createApp()
void OnWindowCreated(CefRefPtr< CefWindow > window) override
CefRefPtr< CefBrowserView > view_
IMPLEMENT_REFCOUNTING(SecurePopupWindowDelegate)
void OnWindowDestroyed(CefRefPtr< CefWindow >) override
SecurePopupWindowDelegate(CefRefPtr< CefBrowserView > view)
static bool get_secure_payments_enabled()
void OnTitleChange(CefRefPtr< CefBrowser > browser, const CefString &) override
void OnBeforeClose(CefRefPtr< CefBrowser > browser) override
static void load_block_page_inline(CefRefPtr< CefBrowser > browser, const std::string &host)
bool OnBeforeBrowse(CefRefPtr< CefBrowser > browser, CefRefPtr< CefFrame > frame, CefRefPtr< CefRequest > request, bool, bool) override
std::vector< CefRefPtr< CefBrowser > > browsers_
void OnAfterCreated(CefRefPtr< CefBrowser > browser) override
bool OnCertificateError(CefRefPtr< CefBrowser > browser, cef_errorcode_t cert_error, const CefString &request_url, CefRefPtr< CefSSLInfo >, CefRefPtr< CefCallback > callback) override
CefRefPtr< CefDisplayHandler > GetDisplayHandler() override
IMPLEMENT_REFCOUNTING(SimpleHandler)
CefRefPtr< CefLifeSpanHandler > GetLifeSpanHandler() override
void inject_watermark(CefRefPtr< CefBrowser > browser)
CefRefPtr< CefRequestHandler > GetRequestHandler() override