Skip to content
API Documentation
BastionGuard™
RootGuardPage.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™ RootGuard GTK integration
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6#include "RootGuardPage.hpp"
8
9#include <glibmm/i18n.h>
10#include <glibmm/markup.h>
11#include <glibmm/main.h>
12
13#include <algorithm>
14#include <filesystem>
15#include <set>
16#include <sstream>
17#include <vector>
18
19namespace {
20
21Gtk::Box* make_status_item(const Glib::ustring& label, Gtk::Label& value)
22{
23 auto* box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
24 box->add_css_class("rootguard-status-item");
25 box->set_hexpand(true);
26
27 auto* name = Gtk::make_managed<Gtk::Label>(label);
28 name->set_halign(Gtk::Align::CENTER);
29 name->add_css_class("rootguard-status-label");
30 box->append(*name);
31
32 value.set_halign(Gtk::Align::CENTER);
33 value.set_wrap(true);
34 value.add_css_class("rootguard-status-value");
35 box->append(value);
36 return box;
37}
38
40{
41 return !event.filename.empty() && event.filename.front() == '/';
42}
43
45{
46 if (event.path_resolution == "protected-baseline")
47 return _("exact path from protected baseline");
48 if (event.path_resolution == "proc-fd")
49 return _("exact path resolved from the process file descriptor");
50 if (event.path_resolution == "process-cwd")
51 return _("exact path resolved from the process working directory");
52 if (event.path_resolution == "kernel-exact")
53 return _("exact path supplied by the kernel event");
54 if (event.path_resolution == "lsm-exact")
55 return _("exact path captured by the LSM before the metadata operation");
56 return _("basename only; the exact path was unavailable in this event");
57}
58
60{
61 const bool blocked = event.verdict == "blocked";
62 const bool audit = event.verdict == "audit";
63
64 if (event.event == "service-blocked")
65 return blocked
66 ? _("System service stopped and runtime-blocked")
67 : _("System service blocking failed");
68 if ((event.reason_flags & RG_REASON_METADATA_BURST) != 0)
69 return _("Unusual permission-change burst detected");
70 if (event.event == "integrity-drift")
71 return _("Protected file integrity changed");
72 if (event.event == "permission-change")
73 return blocked ? _("Permission change blocked")
74 : audit ? _("Permission change observed")
75 : _("Permission change detected");
76 if (event.event == "owner-change")
77 return blocked ? _("Ownership change blocked")
78 : audit ? _("Ownership change observed")
79 : _("Ownership change detected");
80 if (event.event == "protected-unlink")
81 return blocked ? _("Protected file removal blocked")
82 : audit ? _("Protected file removal observed")
83 : _("Protected file removal detected");
84 if (event.event == "protected-rename")
85 return blocked ? _("Protected file rename blocked")
86 : audit ? _("Protected file rename observed")
87 : _("Protected file rename detected");
88 if (event.event == "protected-hardlink")
89 return blocked ? _("Protected hard-link creation blocked")
90 : audit ? _("Protected hard-link creation observed")
91 : _("Protected hard-link creation detected");
92 if (event.event == "xattr-change")
93 return blocked ? _("Extended-attribute change blocked")
94 : audit ? _("Extended-attribute change observed")
95 : _("Extended-attribute change detected");
96 if (event.event == "acl-change")
97 return blocked ? _("ACL change blocked")
98 : audit ? _("ACL change observed")
99 : _("ACL change detected");
100 return blocked ? _("Protected metadata change blocked")
101 : audit ? _("Protected metadata change observed")
102 : _("Protected metadata change detected");
103}
104
106{
107 std::ostringstream output;
108 output << "Affected object: "
109 << (event.filename.empty() ? "--" : event.filename);
110
111 if (has_exact_path(event)) {
112 const std::filesystem::path affected(event.filename);
113 const std::string directory = affected.parent_path().string();
114 output << "\nAffected directory: "
115 << (directory.empty() ? "/" : directory);
116 } else {
117 output << "\nAffected directory: unavailable";
118 }
119
120 output << "\nPath resolution: " << path_resolution_text(event).raw();
121
122 if (event.device != 0 || event.inode != 0)
123 output << "\nTarget identity: " << event.device << ':' << event.inode;
124
125 output << "\nProcess name: "
126 << (event.comm.empty() ? "unknown" : event.comm)
127 << "\nProcess identifiers: PID " << event.pid
128 << " · TGID " << event.tgid
129 << " · PPID " << event.ppid;
130
131 if (!event.parent_comm.empty())
132 output << "\nParent process: " << event.parent_comm
133 << " (PID " << event.ppid << ')';
134 if (!event.parent_executable.empty())
135 output << "\nParent executable: " << event.parent_executable;
136
137 if (!event.actor_executable.empty()) {
138 output << "\nActor executable: " << event.actor_executable;
139 } else {
140 output << "\nActor executable: unresolved";
141 }
142
143 if (event.actor_device != 0 || event.actor_inode != 0)
144 output << "\nActor identity: " << event.actor_device
145 << ':' << event.actor_inode;
146 if (event.old_euid == event.new_euid)
147 output << "\nActor EUID: " << event.old_euid;
148 else
149 output << "\nActor EUID transition: " << event.old_euid
150 << " → " << event.new_euid;
151
152 if (event.old_mode != event.new_mode) {
153 output << "\nMode: " << std::oct << (event.old_mode & 07777U)
154 << " → " << (event.new_mode & 07777U) << std::dec;
155 }
156 if (event.old_uid != event.new_uid || event.old_gid != event.new_gid) {
157 output << "\nOwner: " << event.old_uid << ':' << event.old_gid
158 << " → " << event.new_uid << ':' << event.new_gid;
159 }
160 if ((event.reason_flags & RG_REASON_METADATA_BURST) != 0)
161 output << "\nBurst size in kernel window: " << event.auxiliary;
162 output << "\nVerdict: " << event.verdict
163 << " · Reason: 0x" << std::hex << event.reason_flags << std::dec;
165 output << "\nScope: system service definition";
166 return output.str();
167}
168
170{
171 if (event.rule_id == 0 || !has_exact_path(event))
172 return false;
173
174 constexpr std::uint32_t kBaselineFileReplaced = 1U << 20;
175 constexpr std::uint32_t kProtectedMissing = 1U << 21;
176 if ((event.reason_flags & (kBaselineFileReplaced | kProtectedMissing)) != 0)
177 return false;
178 return event.event == "permission-change" ||
179 event.event == "owner-change" ||
180 event.event == "integrity-drift" ||
181 event.event == "service-blocked";
182}
183
185{
186 if (event.rule_id == 0 || !has_exact_path(event))
187 return false;
188
189 constexpr std::uint32_t kProtectedMissing = 1U << 21;
190 if ((event.reason_flags & kProtectedMissing) != 0)
191 return false;
192
193 return event.event == "permission-change" ||
194 event.event == "owner-change" ||
195 event.event == "xattr-change" ||
196 event.event == "acl-change" ||
197 event.event == "integrity-drift";
198}
199
201{
202 if (event.rule_id == 0)
203 return _("This event belongs to global filesystem surveillance. No protected baseline is attached, so Restore and Quarantine are unavailable.");
204 if (!has_exact_path(event))
205 return _("RootGuard has the filesystem identity but not an exact path. Destructive actions are disabled to avoid acting on the wrong object.");
206 if (event.event == "protected-unlink")
207 return event.verdict == "blocked"
208 ? _("The removal was denied; the file is still present and no restore is required.")
209 : _("The file was removed in audit mode. Automatic restore is impossible without a trusted backup or package copy.");
210 if (event.event == "protected-rename")
211 return _("Automatic recovery is unavailable for rename events. Restore the file from a trusted package or backup.");
212 if (event.event == "protected-hardlink")
213 return _("No automatic action is offered for hard-link events. Review the source and destination manually.");
214 return {};
215}
216
218{
219 if (has_exact_path(event))
220 return event.filename;
221 return event.filename + "\n" + std::to_string(event.device) + ':' +
222 std::to_string(event.inode);
223}
224
226{
227 return event.event == "file-restored" ||
228 event.event == "file-quarantined" ||
229 event.event == "integrity-restored";
230}
231
232void clear_list(Gtk::ListBox& list)
233{
234 while (auto* child = list.get_first_child())
235 list.remove(*child);
236}
237
238void append_path_row(Gtk::ListBox& list, const std::string& path)
239{
240 auto* label = Gtk::make_managed<Gtk::Label>(path);
241 label->set_halign(Gtk::Align::START);
242 label->set_hexpand(true);
243 label->set_selectable(true);
244 label->set_margin(8);
245 label->set_tooltip_text(path);
246 list.append(*label);
247}
248
249std::string trim_copy(std::string value)
250{
251 const auto first = value.find_first_not_of(" \t\r\n");
252 if (first == std::string::npos)
253 return {};
254 const auto last = value.find_last_not_of(" \t\r\n");
255 return value.substr(first, last - first + 1);
256}
257
259{
260 std::ostringstream output;
261 const auto append = [&output](const char marker,
262 const std::vector<std::string>& values) {
263 output << marker << ':' << values.size() << '\n';
264 for (const auto& value : values)
265 output << value << '\n';
266 };
267 append('A', rules.trustedApplications);
268 append('T', rules.trustedExecutables);
269 append('D', rules.deniedExecutables);
270 return output.str();
271}
272
273} // namespace
274
276 : Gtk::Box(Gtk::Orientation::VERTICAL, 8)
277{
278 set_margin(12);
279 set_hexpand(true);
280 set_vexpand(true);
281
282 auto* title_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
283 title_box->add_css_class("secure-headerbar");
284 title_box->set_margin_bottom(4);
285
286 title_.set_text(_("🛡️ RootGuard — Permission and Privilege Protection"));
287 title_.set_margin(8);
288 title_.set_halign(Gtk::Align::CENTER);
289 title_.set_hexpand(true);
290 title_.add_css_class("secure-header-title");
291 title_box->append(title_);
292 append(*title_box);
293
294 auto* summary = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
295 summary->set_homogeneous(true);
296 summary->append(*make_status_item(_("Service"), service_value_));
297 summary->append(*make_status_item(_("Mode"), mode_value_));
298 summary->append(*make_status_item(_("Init system"), init_value_));
299 summary->append(*make_status_item(_("PID"), pid_value_));
300 append(*summary);
301
302 page_stack_.set_hexpand(true);
303 page_stack_.set_vexpand(true);
304 page_stack_.set_hhomogeneous(false);
305 page_stack_.set_vhomogeneous(false);
306
307 page_switcher_.set_stack(page_stack_);
308 page_switcher_.set_halign(Gtk::Align::CENTER);
309 page_switcher_.set_margin_top(2);
310 page_switcher_.set_margin_bottom(2);
311 append(page_switcher_);
312
313 // Overview tab.
314 auto* overview = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
315 overview->set_margin_top(8);
316 overview->set_hexpand(true);
317 overview->set_vexpand(true);
318
319 auto* protection_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
320 protection_box->set_halign(Gtk::Align::CENTER);
321 protection_label_.set_text(_("Immediate permission blocking"));
322 protection_label_.set_tooltip_text(
323 _("Blocks protected system metadata changes before they are committed."));
324 protection_box->append(protection_label_);
325 protection_box->append(protection_switch_);
326
327 protection_info_button_.set_label("ⓘ");
328 protection_info_button_.set_has_frame(false);
329 protection_info_button_.set_tooltip_text(_("Show protection scope"));
330 auto* protection_info = Gtk::make_managed<Gtk::Label>(
331 _("Immediate blocking applies to protected system paths and service definitions. "
332 "User home directories remain audit-only because BastionGuard Anti-Ransomware "
333 "is responsible for enforcement on user data. Routine desktop and browser "
334 "metadata activity can be classified under Application rules without granting "
335 "privilege-transition trust."));
336 protection_info->set_wrap(true);
337 protection_info->set_max_width_chars(58);
338 protection_info->set_margin(14);
339 protection_info->set_halign(Gtk::Align::START);
340 protection_info_popover_.set_autohide(true);
341 protection_info_popover_.set_child(*protection_info);
343 protection_box->append(protection_info_button_);
344 overview->append(*protection_box);
345
346 auto* scope = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
347 scope->set_homogeneous(true);
348 scope->set_margin_start(40);
349 scope->set_margin_end(40);
350 scope->append(*make_status_item(_("Protected system paths"), system_scope_value_));
351 scope->append(*make_status_item(_("User home"), home_scope_value_));
352 overview->append(*scope);
353
354 auto* scope_note = Gtk::make_managed<Gtk::Label>(
355 _("RootGuard records privilege-relevant global metadata. Lower-risk changes remain "
356 "visible as observations; only high-signal incidents generate popups."));
357 scope_note->set_wrap(true);
358 scope_note->set_halign(Gtk::Align::CENTER);
359 scope_note->add_css_class("dim-label");
360 overview->append(*scope_note);
361
362 auto* controls = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
363 controls->set_halign(Gtk::Align::CENTER);
364
365 start_button_.set_label(_("▶ Start"));
366 stop_button_.set_label(_("■ Stop"));
367 reload_button_.set_label(_("↻ Reload policy"));
368 restart_button_.set_label(_("⟳ Restart"));
369 refresh_button_.set_label(_("Refresh"));
370 start_button_.add_css_class("btn-primary");
371 stop_button_.add_css_class("btn-danger");
372 reload_button_.add_css_class("btn-warning");
373
374 controls->append(start_button_);
375 controls->append(stop_button_);
376 controls->append(reload_button_);
377 controls->append(restart_button_);
378 controls->append(refresh_button_);
379 overview->append(*controls);
380
381 auto* overview_spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
382 overview_spacer->set_vexpand(true);
383 overview->append(*overview_spacer);
384 page_stack_.add(*overview, "overview", _("Overview"));
385
386 // Incidents tab.
387 auto* incidents_panel = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
388 incidents_panel->set_hexpand(true);
389 incidents_panel->set_vexpand(true);
390 incidents_panel->set_margin_top(8);
391
392 incidents_title_.set_text(_("Security observations and active incidents"));
393 incidents_title_.set_halign(Gtk::Align::START);
394 incidents_title_.add_css_class("BastionGuard-title");
395 incidents_panel->append(incidents_title_);
396
397 incidents_list_.set_selection_mode(Gtk::SelectionMode::NONE);
398 incidents_list_.add_css_class("rootguard-incident-list");
399 incidents_scroller_.set_hexpand(true);
400 incidents_scroller_.set_vexpand(true);
401 incidents_scroller_.set_min_content_height(280);
402 incidents_scroller_.set_policy(Gtk::PolicyType::AUTOMATIC,
403 Gtk::PolicyType::AUTOMATIC);
405 incidents_panel->append(incidents_scroller_);
406 page_stack_.add(*incidents_panel, "incidents", _("Incidents"));
407
408 // Events tab.
409 auto* events_panel = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
410 events_panel->set_hexpand(true);
411 events_panel->set_vexpand(true);
412 events_panel->set_margin_top(8);
413
414 auto* events_header = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
415 auto* events_label = Gtk::make_managed<Gtk::Label>(_("Recent RootGuard events"));
416 events_label->set_halign(Gtk::Align::START);
417 events_label->set_hexpand(true);
418 events_label->add_css_class("BastionGuard-title");
419 show_trusted_.set_label(_("Show trusted events"));
420 show_trusted_.set_active(true);
421 show_trusted_.set_tooltip_text(
422 _("Trusted application activity is shown by default for maximum transparency. "
423 "Disable this option only to focus on audit and blocked events."));
424 events_header->append(*events_label);
425 events_header->append(show_trusted_);
426 events_panel->append(*events_header);
427
428 events_view_.set_editable(false);
429 events_view_.set_cursor_visible(false);
430 events_view_.set_monospace(true);
431 events_view_.set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
432 events_view_.set_left_margin(8);
433 events_view_.set_right_margin(8);
434 events_view_.set_top_margin(8);
435 events_view_.set_bottom_margin(8);
436 events_view_.add_css_class("rootguard-terminal");
437
438 events_scroller_.set_hexpand(true);
439 events_scroller_.set_vexpand(true);
440 events_scroller_.set_min_content_height(280);
441 events_scroller_.set_policy(Gtk::PolicyType::AUTOMATIC,
442 Gtk::PolicyType::AUTOMATIC);
444 events_panel->append(events_scroller_);
445 page_stack_.add(*events_panel, "events", _("Events"));
446
447 // Application rules tab, with nested tabs to keep the page compact.
448 auto* rules_page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
449 rules_page->set_hexpand(true);
450 rules_page->set_vexpand(true);
451 rules_page->set_margin_top(8);
452
453 auto* rules_intro = Gtk::make_managed<Gtk::Label>(
454 _("Manage executable identities used by RootGuard. Saving validates the policy, "
455 "requests administrator authentication, and restarts RootGuard so the new inode "
456 "identities take effect immediately."));
457 rules_intro->set_wrap(true);
458 rules_intro->set_halign(Gtk::Align::START);
459 rules_page->append(*rules_intro);
460
461 auto* defaults_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
462 defaults_row->set_halign(Gtk::Align::END);
463 auto* add_defaults = Gtk::make_managed<Gtk::Button>(
464 _("Add installed desktop/browser defaults"));
465 add_defaults->set_tooltip_text(
466 _("Adds only known executables that exist on this computer. Review the list before saving."));
467 defaults_row->append(*add_defaults);
468 rules_page->append(*defaults_row);
469
470 rules_stack_.set_hexpand(true);
471 rules_stack_.set_vexpand(true);
472 rules_stack_.set_hhomogeneous(false);
473 rules_stack_.set_vhomogeneous(false);
474 rules_switcher_.set_stack(rules_stack_);
475 rules_switcher_.set_halign(Gtk::Align::CENTER);
476 rules_page->append(rules_switcher_);
477
478 rules_stack_.add(
480 _("Trusted applications"),
481 _("Routine desktop, file-manager and browser compatibility activity. Matching "
482 "legacy global events can become trusted, but direct privilege-metadata signals "
483 "are never suppressed by this list. This list never grants privilege trust."),
485 "trusted-applications", _("Trusted apps"));
486
487 rules_stack_.add(
489 _("Privilege-trusted executables"),
490 _("Executables allowed as legitimate actors in RootGuard privilege-transition "
491 "checks. Existing files are still subject to ownership and identity validation."),
493 "trusted-executables", _("Privilege trusted"));
494
495 rules_stack_.add(
497 _("Blocked executables"),
498 _("Executables blocked during RootGuard privilege-transition checks. This is not "
499 "a general application-launch blacklist."),
501 "denied-executables", _("Blocked"));
502
503 // The switcher only controls the stack; the stack itself must also be
504 // inserted into the widget hierarchy or its rule editors remain invisible.
505 rules_page->append(rules_stack_);
506
507 auto* save_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
508 save_row->set_halign(Gtk::Align::END);
509 save_rules_button_.set_label(_("Save rules and restart RootGuard"));
510 save_rules_button_.add_css_class("btn-primary");
511 save_rules_button_.set_sensitive(false);
512 save_row->append(save_rules_button_);
513 rules_page->append(*save_row);
514
515 rules_status_.set_halign(Gtk::Align::START);
516 rules_status_.set_wrap(true);
517 rules_status_.add_css_class("dim-label");
518 rules_status_.set_text(
519 _("Unsaved changes are never applied silently. RootGuard restarts only after a successful policy save."));
520 rules_page->append(rules_status_);
521 page_stack_.add(*rules_page, "application-rules", _("Application rules"));
522
523 append(page_stack_);
524
525 status_.set_halign(Gtk::Align::CENTER);
526 status_.set_wrap(true);
527 status_.set_margin_top(2);
528 append(status_);
529
531 sigc::mem_fun(*this, &RootGuardPage::on_snapshot));
532
533 start_button_.signal_clicked().connect(
534 [this] { status_.set_text(_("Requesting service start…")); client_.start_service(); });
535 stop_button_.signal_clicked().connect(
536 [this] { status_.set_text(_("Requesting service stop…")); client_.stop_service(); });
537 reload_button_.signal_clicked().connect(
538 [this] { status_.set_text(_("Reloading RootGuard policy…")); client_.reload_policy(); });
539 restart_button_.signal_clicked().connect(
540 [this] { status_.set_text(_("Restarting RootGuard…")); client_.restart_service(); });
541 refresh_button_.signal_clicked().connect(
542 [this] { status_.set_text(_("Refreshing RootGuard status…")); client_.refresh(); });
543 show_trusted_.signal_toggled().connect([this] {
544 // Filtering is a local presentation choice. Re-render the most recent
545 // snapshot immediately instead of waiting for the asynchronous client
546 // refresh/next timer tick.
547 if (last_snapshot_) {
549 return;
550 }
552 });
553 protection_switch_.property_active().signal_changed().connect(
554 sigc::mem_fun(*this, &RootGuardPage::on_protection_toggled));
555 save_rules_button_.signal_clicked().connect(
556 sigc::mem_fun(*this, &RootGuardPage::save_application_rules));
557 add_defaults->signal_clicked().connect(
559
560 auto_refresh_connection_ = Glib::signal_timeout().connect_seconds(
561 sigc::mem_fun(*this, &RootGuardPage::on_auto_refresh), 1);
562
563 service_value_.set_text(_("Checking…"));
564 mode_value_.set_text(Glib::ustring("—"));
565 init_value_.set_text(Glib::ustring("—"));
566 pid_value_.set_text(Glib::ustring("—"));
567 system_scope_value_.set_text(_("Checking…"));
568 home_scope_value_.set_text(_("Audit-only"));
569 status_.set_text(_("Loading RootGuard status and recent security events…"));
571}
572
574{
575 alive_->store(false, std::memory_order_release);
576 if (client_update_connection_.connected())
577 client_update_connection_.disconnect();
578 if (auto_refresh_connection_.connected())
579 auto_refresh_connection_.disconnect();
580 while (!alert_queue_.empty())
581 alert_queue_.pop();
582 if (current_alert_)
583 current_alert_->hide();
584 current_alert_.reset();
585}
586
587Gtk::Box* RootGuardPage::make_rule_editor(const Glib::ustring& title,
588 const Glib::ustring& description,
589 Gtk::ListBox& list,
590 Gtk::Entry& entry)
591{
592 auto* editor = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
593 editor->set_hexpand(true);
594 editor->set_vexpand(true);
595 editor->set_margin_top(6);
596
597 auto* heading = Gtk::make_managed<Gtk::Label>(title);
598 heading->set_halign(Gtk::Align::START);
599 heading->add_css_class("BastionGuard-title");
600 editor->append(*heading);
601
602 auto* explanation = Gtk::make_managed<Gtk::Label>(description);
603 explanation->set_halign(Gtk::Align::START);
604 explanation->set_wrap(true);
605 explanation->add_css_class("dim-label");
606 editor->append(*explanation);
607
608 list.set_selection_mode(Gtk::SelectionMode::SINGLE);
609 list.add_css_class("rootguard-rule-list");
610 auto* scroller = Gtk::make_managed<Gtk::ScrolledWindow>();
611 scroller->set_hexpand(true);
612 scroller->set_vexpand(true);
613 scroller->set_min_content_height(210);
614 scroller->set_policy(Gtk::PolicyType::AUTOMATIC,
615 Gtk::PolicyType::AUTOMATIC);
616 scroller->set_child(list);
617 editor->append(*scroller);
618
619 auto* input_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
620 entry.set_hexpand(true);
621 entry.set_placeholder_text(_("Absolute executable path, for example /usr/bin/firefox"));
622 auto* add = Gtk::make_managed<Gtk::Button>(_("Add"));
623 auto* remove = Gtk::make_managed<Gtk::Button>(_("Remove selected"));
624 add->add_css_class("btn-primary");
625 remove->add_css_class("btn-danger");
626 input_row->append(entry);
627 input_row->append(*add);
628 input_row->append(*remove);
629 editor->append(*input_row);
630
631 add->signal_clicked().connect([this, &entry, &list] {
632 add_rule_from_entry(entry, list);
633 });
634 entry.signal_activate().connect([this, &entry, &list] {
635 add_rule_from_entry(entry, list);
636 });
637 remove->signal_clicked().connect([this, &list] {
639 });
640 return editor;
641}
642
643void RootGuardPage::add_rule_from_entry(Gtk::Entry& entry, Gtk::ListBox& list)
644{
645 const std::string raw = trim_copy(entry.get_text().raw());
646 if (raw.empty()) {
647 rules_status_.set_text(_("Enter an absolute executable path first."));
648 return;
649 }
650
651 std::filesystem::path candidate(raw);
652 if (!candidate.is_absolute()) {
653 rules_status_.set_text(_("Application rules require an absolute path beginning with /."));
654 return;
655 }
656
657 candidate = candidate.lexically_normal();
658 const std::string normalized = candidate.string();
659 const auto current = collect_rules(list);
660 if (std::find(current.begin(), current.end(), normalized) != current.end()) {
661 rules_status_.set_text(_("That executable path is already present in this list."));
662 return;
663 }
664
665 append_path_row(list, normalized);
666 entry.set_text("");
668}
669
671{
672 static const std::vector<std::string> candidates {
673 "/usr/bin/gnome-shell",
674 "/usr/bin/nautilus",
675 "/usr/bin/plasmashell",
676 "/usr/bin/kwin_wayland",
677 "/usr/bin/dolphin",
678 "/usr/bin/xfdesktop",
679 "/usr/bin/xfwm4",
680 "/usr/bin/thunar",
681 "/usr/bin/cinnamon",
682 "/usr/bin/nemo",
683 "/usr/bin/mate-panel",
684 "/usr/bin/caja",
685 "/usr/bin/firefox",
686 "/usr/lib/firefox/firefox",
687 "/usr/bin/chromium",
688 "/usr/bin/google-chrome-stable",
689 "/usr/bin/brave",
690 "/usr/bin/vivaldi-stable",
691 };
692
694 std::set<std::string> present(current.begin(), current.end());
695 std::size_t added = 0;
696 for (const auto& candidate : candidates) {
697 std::error_code error;
698 const auto status = std::filesystem::status(candidate, error);
699 if (error || !std::filesystem::is_regular_file(status))
700 continue;
701 const auto permissions = status.permissions();
702 const bool executable =
703 (permissions & std::filesystem::perms::owner_exec) !=
704 std::filesystem::perms::none ||
705 (permissions & std::filesystem::perms::group_exec) !=
706 std::filesystem::perms::none ||
707 (permissions & std::filesystem::perms::others_exec) !=
708 std::filesystem::perms::none;
709 if (!executable)
710 continue;
711
712 const auto canonical = std::filesystem::weakly_canonical(candidate, error);
713 const std::string normalized = error
714 ? std::filesystem::path(candidate).lexically_normal().string()
715 : canonical.string();
716 if (!present.insert(normalized).second)
717 continue;
718 append_path_row(trusted_applications_list_, normalized);
719 ++added;
720 }
721
722 if (added == 0) {
723 rules_status_.set_text(
724 _("No new installed desktop or browser defaults were found."));
725 return;
726 }
728 rules_status_.set_text(Glib::ustring::compose(
729 _("Added %1 installed desktop/browser executable(s). Review and save to restart RootGuard."),
730 added));
731}
732
734{
735 auto* row = list.get_selected_row();
736 if (!row) {
737 rules_status_.set_text(_("Select a rule to remove."));
738 return;
739 }
740 list.remove(*row);
742}
743
745{
746 rules_dirty_ = true;
747 rules_status_.set_text(
748 _("Rules have unsaved changes. Saving will validate the policy and restart RootGuard."));
749 save_rules_button_.set_sensitive(!rules_saving_);
750}
751
752std::vector<std::string> RootGuardPage::collect_rules(const Gtk::ListBox& list) const
753{
754 std::vector<std::string> values;
755 for (auto* widget = list.get_first_child(); widget;
756 widget = widget->get_next_sibling()) {
757 const auto* row = dynamic_cast<const Gtk::ListBoxRow*>(widget);
758 if (!row)
759 continue;
760 const auto* label = dynamic_cast<const Gtk::Label*>(row->get_child());
761 if (!label)
762 continue;
763 const std::string value = trim_copy(label->get_text().raw());
764 if (!value.empty())
765 values.push_back(value);
766 }
767 std::sort(values.begin(), values.end());
768 values.erase(std::unique(values.begin(), values.end()), values.end());
769 return values;
770}
771
774{
775 const std::string signature = rules_signature(rules);
776 if (rules_dirty_ || rules_saving_ || signature == rules_signature_)
777 return;
778
779 const auto populate = [](Gtk::ListBox& list,
780 std::vector<std::string> values) {
781 clear_list(list);
782 std::sort(values.begin(), values.end());
783 values.erase(std::unique(values.begin(), values.end()), values.end());
784 for (const auto& path : values)
785 append_path_row(list, path);
786 };
787
791 rules_signature_ = signature;
792}
793
795{
800
801 std::set<std::string> trusted;
802 trusted.insert(rules.trustedApplications.begin(), rules.trustedApplications.end());
803 trusted.insert(rules.trustedExecutables.begin(), rules.trustedExecutables.end());
804 for (const auto& denied : rules.deniedExecutables) {
805 if (trusted.count(denied) != 0) {
806 rules_status_.set_text(
807 _("The same executable cannot be both trusted and blocked."));
808 return;
809 }
810 }
811
812 rules_saving_ = true;
813 save_rules_button_.set_sensitive(false);
814 rules_status_.set_text(
815 _("Saving application rules and restarting RootGuard… Administrator authentication may be requested."));
816 status_.set_text(_("Saving RootGuard application rules…"));
817 client_.save_application_rules(std::move(rules));
818}
819
821{
823 return true;
824}
825
827{
829 return;
830 protection_switch_.set_sensitive(false);
831 const bool enabled = protection_switch_.get_active();
832 status_.set_text(enabled
833 ? _("Enabling immediate blocking for protected system paths; user home remains audit-only…")
834 : _("Switching RootGuard system-path protection to audit mode…"));
836}
837
839{
840 // Keep a local copy so event filtering reacts immediately when the user
841 // toggles trusted-event visibility.
842 last_snapshot_ = snapshot;
843
844 service_value_.set_text(snapshot.service.active ? _("Active") : _("Inactive"));
845 mode_value_.set_text(snapshot.service.immediateProtection
846 ? _("Immediate block")
847 : snapshot.service.mode.empty() ? "unknown" : snapshot.service.mode);
848 init_value_.set_text(snapshot.service.init_system.empty()
849 ? "unknown"
850 : snapshot.service.init_system);
851 pid_value_.set_text(snapshot.service.pid.empty() ? "—" : snapshot.service.pid);
852
854 ? _("Immediate blocking") : _("Audit-only"));
855 home_scope_value_.set_text(snapshot.service.blockUserHome
856 ? _("Blocking enabled by manual policy")
857 : _("Audit-only · Anti-Ransomware enforcement"));
858
859 start_button_.set_sensitive(!snapshot.service.active);
860 stop_button_.set_sensitive(snapshot.service.active);
861 reload_button_.set_sensitive(snapshot.service.active);
862 restart_button_.set_sensitive(snapshot.service.active);
863
864 updating_switch_ = true;
866 updating_switch_ = false;
867 protection_switch_.set_sensitive(snapshot.service.active && !rules_saving_);
868
869 if (rules_saving_ && !snapshot.operation_message.empty()) {
870 rules_saving_ = false;
871 if (snapshot.operation_success) {
872 rules_dirty_ = false;
873 rules_signature_.clear();
874 rules_status_.set_text(
875 _("Application rules saved and RootGuard restarted successfully."));
876 } else {
877 rules_status_.set_text(Glib::ustring::compose(
878 _("Application rules were not applied: %1"),
879 snapshot.operation_message));
880 }
881 }
884
885 if (!snapshot.operation_message.empty()) {
886 status_.set_text(snapshot.operation_success
887 ? Glib::ustring::compose(_("✅ %1"), snapshot.operation_message)
888 : Glib::ustring::compose(_("❌ %1"), snapshot.operation_message));
889 } else if (snapshot.service.active && snapshot.service.immediateProtection) {
890 status_.set_text(snapshot.service.autoBlockServices
891 ? _("RootGuard blocks protected system changes and contains affected services. User home remains audit-only under Anti-Ransomware protection.")
892 : _("RootGuard blocks protected system metadata changes. User home remains audit-only under Anti-Ransomware protection."));
893 } else if (snapshot.service.active) {
894 status_.set_text(
895 _("RootGuard is monitoring filesystem metadata in audit mode. Global privilege events are sampled and destructive permission bursts are aggregated."));
896 } else {
897 status_.set_text(_("RootGuard is not running."));
898 }
899
900 render_incidents(snapshot);
901 render_events(snapshot);
902 queue_new_alerts(snapshot);
903}
904
906{
907 clear_list(incidents_list_);
908
909 std::vector<BastionGuard::RootGuard::Event> incidents;
910 std::set<std::string> closed_paths;
911 std::set<std::string> displayed_paths;
912 for (auto iterator = snapshot.events.rbegin(); iterator != snapshot.events.rend(); ++iterator) {
913 if (closes_incident(*iterator) && !iterator->filename.empty()) {
914 closed_paths.insert(incident_key(*iterator));
915 continue;
916 }
918 continue;
919 if (closed_paths.count(incident_key(*iterator)) != 0 ||
920 displayed_paths.count(incident_key(*iterator)) != 0 ||
921 dismissed_paths_.count(incident_key(*iterator)) != 0)
922 continue;
923 if (dismissed_events_.count(
925 continue;
926 displayed_paths.insert(incident_key(*iterator));
927 incidents.push_back(*iterator);
928 if (incidents.size() >= 60)
929 break;
930 }
931
932 if (incidents.empty()) {
933 auto* empty = Gtk::make_managed<Gtk::Label>(
934 _("No unresolved observations or blocked changes."));
935 empty->set_margin(16);
936 empty->set_halign(Gtk::Align::CENTER);
937 incidents_list_.append(*empty);
938 return;
939 }
940
941 for (const auto& event : incidents) {
942 auto* row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 8);
943 row->set_margin(8);
944 row->add_css_class("rootguard-incident-row");
945
946 auto* title = Gtk::make_managed<Gtk::Label>(incident_title(event));
947 title->set_halign(Gtk::Align::START);
948 const auto severity = BastionGuard::RootGuard::classify_event(event);
949 if (event.verdict == "blocked")
950 title->add_css_class("rootguard-blocked");
952 title->add_css_class("rootguard-alert");
953 else
954 title->add_css_class("dim-label");
955 row->append(*title);
956
957 auto* detail = Gtk::make_managed<Gtk::Label>(incident_detail(event));
958 detail->set_halign(Gtk::Align::START);
959 detail->set_wrap(true);
960 detail->set_selectable(true);
961 row->append(*detail);
962
963 const bool audit_event = event.verdict == "audit";
964 const bool can_restore = !audit_event && restore_available(event);
965 const bool can_remove = !audit_event && remove_available(event);
966
967 if (!can_restore && !can_remove) {
968 const auto note_text = audit_event
969 ? Glib::ustring(
970 _("Audit observation: RootGuard did not block or alter the operation."))
971 : recovery_note(event);
972 if (!note_text.empty()) {
973 auto* note = Gtk::make_managed<Gtk::Label>(note_text);
974 note->set_halign(Gtk::Align::START);
975 note->set_wrap(true);
976 note->add_css_class("dim-label");
977 row->append(*note);
978 }
979 }
980
981 auto* actions = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
982 actions->set_halign(Gtk::Align::END);
983
984 if (can_restore) {
985 auto* restore = Gtk::make_managed<Gtk::Button>(_("Restore"));
986 restore->add_css_class("btn-primary");
987 restore->signal_clicked().connect([this, event] { restore_event(event); });
988 actions->append(*restore);
989 }
990 if (can_remove) {
991 auto* remove = Gtk::make_managed<Gtk::Button>(_("Quarantine"));
992 remove->add_css_class("btn-danger");
993 remove->signal_clicked().connect([this, event] { remove_event(event); });
994 actions->append(*remove);
995 }
996
997 auto* dismiss = Gtk::make_managed<Gtk::Button>(_("Dismiss"));
998 dismiss->signal_clicked().connect([this, event] { dismiss_event(event); });
999 actions->append(*dismiss);
1000 row->append(*actions);
1001 incidents_list_.append(*row);
1002 }
1003}
1004
1006{
1007 std::ostringstream output;
1008 std::size_t visible = 0;
1009 for (auto iterator = snapshot.events.rbegin(); iterator != snapshot.events.rend(); ++iterator) {
1010 if (!show_trusted_.get_active() && iterator->verdict == "trusted")
1011 continue;
1012 output << BastionGuard::RootGuard::format_event(*iterator) << '\n';
1013 if (++visible >= 150)
1014 break;
1015 }
1016 if (visible == 0) {
1017 if (snapshot.events.empty()) {
1018 output << _("No RootGuard events are available yet.") << '\n';
1019 } else {
1020 output << _("Only trusted events are available. Enable “Show trusted events” to display them.")
1021 << '\n';
1022 }
1023 }
1024 events_view_.get_buffer()->set_text(output.str());
1025}
1026
1028{
1029 std::vector<BastionGuard::RootGuard::Event> new_events;
1030 for (const auto& event : snapshot.events) {
1031 if (event.verdict == "trusted")
1032 continue;
1033 if (closes_incident(event) && !event.filename.empty()) {
1034 notified_paths_.erase(incident_key(event));
1035 dismissed_paths_.erase(incident_key(event));
1036 continue;
1037 }
1039 continue;
1040
1041 const std::string fingerprint =
1043 if (!seen_events_.insert(fingerprint).second)
1044 continue;
1045
1046 const bool service_failure = event.event == "service-blocked" &&
1047 event.verdict != "blocked";
1048 const bool first_for_path = notified_paths_.insert(incident_key(event)).second;
1049 if (event.event != "service-blocked")
1050 dismissed_paths_.erase(incident_key(event));
1051
1052 if (!initial_snapshot_ &&
1053 dismissed_events_.count(fingerprint) == 0 &&
1054 (first_for_path || service_failure)) {
1055 new_events.push_back(event);
1056 }
1057 }
1058 initial_snapshot_ = false;
1059
1060 std::set<std::string> selected_paths;
1061 std::vector<BastionGuard::RootGuard::Event> selected;
1062 for (auto iterator = new_events.rbegin(); iterator != new_events.rend(); ++iterator) {
1063 const std::string key = incident_key(*iterator) + "\n" +
1064 (iterator->event == "service-blocked" && iterator->verdict != "blocked"
1065 ? "failure" : "incident");
1066 if (selected_paths.insert(key).second)
1067 selected.push_back(*iterator);
1068 }
1069 /*
1070 * Never build a popup backlog. Protected/service incidents take priority and
1071 * may interrupt immediately; a global metadata burst is deliberately much
1072 * quieter because backup/package activity can resemble a burst. The full
1073 * history always remains in Incidents/Events.
1074 */
1075 const auto critical = std::find_if(
1076 selected.begin(), selected.end(), [](const auto& event) {
1077 return event.rule_id != 0 || event.event == "service-blocked";
1078 });
1079 if (!current_alert_ && alert_queue_.empty() && critical != selected.end()) {
1080 alert_queue_.push(*critical);
1081 } else if (!current_alert_ && alert_queue_.empty() && !selected.empty()) {
1082 constexpr auto globalBurstPopupInterval = std::chrono::minutes(15);
1083 const auto now = std::chrono::steady_clock::now();
1084 const auto burst = std::find_if(
1085 selected.begin(), selected.end(), [](const auto& event) {
1086 return event.rule_id == 0 &&
1087 (event.reason_flags & RG_REASON_METADATA_BURST) != 0;
1088 });
1089 const bool cooldownExpired =
1090 last_global_burst_alert_shown_.time_since_epoch().count() == 0 ||
1091 now - last_global_burst_alert_shown_ >= globalBurstPopupInterval;
1092 if (burst != selected.end() && cooldownExpired)
1093 alert_queue_.push(*burst);
1094 }
1095
1096 if (!current_alert_)
1098}
1099
1101{
1102 if (current_alert_ || alert_queue_.empty())
1103 return;
1104
1105 const auto event = alert_queue_.front();
1106 alert_queue_.pop();
1107 if (event.rule_id == 0 &&
1108 (event.reason_flags & RG_REASON_METADATA_BURST) != 0)
1109 last_global_burst_alert_shown_ = std::chrono::steady_clock::now();
1110 current_alert_ = std::make_unique<Gtk::Window>();
1111 auto* win = current_alert_.get();
1112 win->set_title("");
1113 win->set_default_size(720, 520);
1114 win->set_resizable(true);
1115 win->set_modal(false);
1116 win->add_css_class("BastionGuard-popup");
1117 win->add_css_class("BastionGuard-dialog");
1118 if (auto* root = dynamic_cast<Gtk::Window*>(get_root()))
1119 win->set_transient_for(*root);
1120
1121 auto* header = Gtk::make_managed<Gtk::HeaderBar>();
1122 header->set_show_title_buttons(false);
1123 header->set_decoration_layout("");
1124 header->add_css_class("custom-headerbar");
1125 auto* header_title = Gtk::make_managed<Gtk::Label>(
1126 event.event == "service-blocked"
1127 ? (event.verdict == "blocked"
1128 ? _("🛑 System service blocked")
1129 : _("⚠️ System service block failed"))
1130 : (event.reason_flags & RG_REASON_METADATA_BURST) != 0
1131 ? _("⚠️ Unusual permission-change burst")
1132 : event.rule_id == 0
1133 ? _("⚠️ Filesystem metadata change observed")
1134 : event.verdict == "blocked"
1135 ? _("🛑 Protected change blocked")
1136 : event.verdict == "audit"
1137 ? _("⚠️ Protected change observed")
1138 : _("⚠️ Protected change detected"));
1139 header_title->add_css_class("BastionGuard-title");
1140 header->pack_start(*header_title);
1141 win->set_titlebar(*header);
1142
1143 auto* content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
1144 content->set_margin(18);
1145 win->set_child(*content);
1146
1147 const Glib::ustring message_template =
1148 (event.reason_flags & RG_REASON_METADATA_BURST) != 0
1149 ? _("RootGuard detected many destructive permission/ownership changes in a short kernel window. Latest object:\n<b>%1</b>")
1150 : event.rule_id == 0
1151 ? _("RootGuard observed a filesystem metadata change to:\n<b>%1</b>")
1152 : event.verdict == "blocked"
1153 ? _("RootGuard blocked a change to:\n<b>%1</b>")
1154 : event.verdict == "audit"
1155 ? _("RootGuard observed a change to:\n<b>%1</b>")
1156 : _("RootGuard detected a change to:\n<b>%1</b>");
1157 auto* message = Gtk::make_managed<Gtk::Label>(
1158 Glib::ustring::compose(
1159 message_template,
1160 Glib::Markup::escape_text(event.filename.empty() ? "--" : event.filename)));
1161 message->set_use_markup(true);
1162 message->set_wrap(true);
1163 message->set_selectable(true);
1164 message->set_halign(Gtk::Align::CENTER);
1165 content->append(*message);
1166
1167 auto* detail = Gtk::make_managed<Gtk::Label>(incident_detail(event));
1168 detail->set_wrap(true);
1169 detail->set_selectable(true);
1170 detail->set_halign(Gtk::Align::START);
1171 detail->set_valign(Gtk::Align::START);
1172 detail->set_xalign(0.0F);
1173
1174 auto* detail_scroller = Gtk::make_managed<Gtk::ScrolledWindow>();
1175 detail_scroller->set_hexpand(true);
1176 detail_scroller->set_vexpand(true);
1177 detail_scroller->set_min_content_height(220);
1178 detail_scroller->set_policy(Gtk::PolicyType::AUTOMATIC,
1179 Gtk::PolicyType::AUTOMATIC);
1180 detail_scroller->set_child(*detail);
1181 content->append(*detail_scroller);
1182
1183 if (!has_exact_path(event)) {
1184 auto* path_notice = Gtk::make_managed<Gtk::Label>(
1185 _("Transparency notice: the kernel event contained only a basename. RootGuard "
1186 "shows the filesystem identity and disables path-based actions rather than "
1187 "guessing an unsafe path."));
1188 path_notice->set_wrap(true);
1189 path_notice->set_halign(Gtk::Align::CENTER);
1190 path_notice->add_css_class("dim-label");
1191 content->append(*path_notice);
1192 }
1193
1195 auto* service_notice = Gtk::make_managed<Gtk::Label>(
1196 event.event == "service-blocked"
1197 ? (event.verdict == "blocked"
1198 ? _("The affected system service has been stopped immediately. On systemd it is also runtime-masked.")
1199 : _("RootGuard could not stop the affected service. Review the init-system logs immediately."))
1200 : event.verdict == "blocked"
1201 ? _("RootGuard requested immediate service containment. A follow-up event will confirm the init-system action.")
1202 : _("Audit mode never stops or masks services. This event is informational only."));
1203 service_notice->set_wrap(true);
1204 service_notice->add_css_class("rootguard-blocked");
1205 service_notice->set_halign(Gtk::Align::CENTER);
1206 content->append(*service_notice);
1207 }
1208
1209 const bool audit_event = event.verdict == "audit";
1210 const bool can_restore = !audit_event && restore_available(event);
1211 const bool can_remove = !audit_event && remove_available(event);
1212 const auto note_text = audit_event
1213 ? Glib::ustring(
1214 _("Audit mode recorded this event without blocking or altering the operation. This notification is acknowledgement-only."))
1215 : recovery_note(event);
1216 if (!note_text.empty() && !can_restore && !can_remove) {
1217 auto* note = Gtk::make_managed<Gtk::Label>(note_text);
1218 note->set_wrap(true);
1219 note->set_halign(Gtk::Align::CENTER);
1220 note->add_css_class("dim-label");
1221 content->append(*note);
1222 }
1223
1224 auto* buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
1225 buttons->set_halign(Gtk::Align::CENTER);
1226
1227 auto* acknowledge = Gtk::make_managed<Gtk::Button>(
1228 event.verdict == "blocked" ? _("Keep blocked") : _("Acknowledge"));
1229 buttons->append(*acknowledge);
1230
1231 if (can_restore) {
1232 auto* restore = Gtk::make_managed<Gtk::Button>(_("Restore"));
1233 restore->add_css_class("btn-primary");
1234 restore->signal_clicked().connect([this, event] {
1235 restore_event(event);
1237 });
1238 buttons->append(*restore);
1239 }
1240 if (can_remove) {
1241 auto* remove = Gtk::make_managed<Gtk::Button>(_("Quarantine"));
1242 remove->add_css_class("btn-danger");
1243 remove->signal_clicked().connect([this, event] {
1244 remove_event(event);
1246 });
1247 buttons->append(*remove);
1248 }
1249 content->append(*buttons);
1250
1251 acknowledge->signal_clicked().connect([this, event] {
1252 dismiss_alert(event);
1254 });
1255 win->signal_close_request().connect([this, event]() -> bool {
1256 dismiss_alert(event);
1258 return true;
1259 }, false);
1260 win->present();
1261}
1262
1264{
1265 if (!current_alert_)
1266 return;
1267 auto* window = current_alert_.get();
1268 window->hide();
1269 const auto alive = alive_;
1270 Glib::signal_idle().connect_once([this, window, alive] {
1271 if (!alive->load(std::memory_order_acquire))
1272 return;
1273 if (current_alert_.get() == window)
1274 current_alert_.reset();
1276 });
1277}
1278
1283
1290
1292{
1293 status_.set_text(_("Restoring protected metadata and unblocking the service…"));
1294 client_.restore_event(event);
1295}
1296
1298{
1299 status_.set_text(
1300 _("Moving the protected file from its original path into quarantine…"));
1301 client_.remove_event(event);
1302}
#define _(s)
sigc::signal< void(const Snapshot &)> & signal_updated() noexcept
void save_application_rules(ApplicationRules rules)
void remove_event(const Event &event)
void restore_event(const Event &event)
void set_immediate_protection(bool enabled)
Gtk::CheckButton show_trusted_
std::optional< BastionGuard::RootGuard::Snapshot > last_snapshot_
Gtk::Entry trusted_executables_entry_
Gtk::Button refresh_button_
std::set< std::string > notified_paths_
Gtk::Label incidents_title_
void on_snapshot(const BastionGuard::RootGuard::Snapshot &snapshot)
std::set< std::string > dismissed_paths_
Gtk::ListBox denied_executables_list_
void on_protection_toggled()
void queue_new_alerts(const BastionGuard::RootGuard::Snapshot &snapshot)
void save_application_rules()
Gtk::Label home_scope_value_
Gtk::Entry denied_executables_entry_
Gtk::Label title_
std::string rules_signature_
Gtk::Button save_rules_button_
sigc::connection client_update_connection_
Gtk::StackSwitcher rules_switcher_
std::set< std::string > seen_events_
void render_incidents(const BastionGuard::RootGuard::Snapshot &snapshot)
void remove_event(const BastionGuard::RootGuard::Event &event)
Gtk::Label mode_value_
Gtk::Button start_button_
void dismiss_alert(const BastionGuard::RootGuard::Event &event)
Gtk::MenuButton protection_info_button_
Gtk::Button stop_button_
Gtk::Label init_value_
std::shared_ptr< std::atomic_bool > alive_
std::vector< std::string > collect_rules(const Gtk::ListBox &list) const
Gtk::Entry trusted_applications_entry_
Gtk::Label service_value_
std::chrono::steady_clock::time_point last_global_burst_alert_shown_
void restore_event(const BastionGuard::RootGuard::Event &event)
void render_events(const BastionGuard::RootGuard::Snapshot &snapshot)
Gtk::Stack page_stack_
Gtk::ListBox trusted_applications_list_
~RootGuardPage() override
Gtk::ListBox trusted_executables_list_
Gtk::Button restart_button_
sigc::connection auto_refresh_connection_
Gtk::ListBox incidents_list_
Gtk::StackSwitcher page_switcher_
Gtk::Button reload_button_
void populate_application_rules(const BastionGuard::RootGuard::ApplicationRules &rules)
Gtk::Box * make_rule_editor(const Glib::ustring &title, const Glib::ustring &description, Gtk::ListBox &list, Gtk::Entry &entry)
std::queue< BastionGuard::RootGuard::Event > alert_queue_
Gtk::Label system_scope_value_
Gtk::Stack rules_stack_
Gtk::Switch protection_switch_
Gtk::ScrolledWindow events_scroller_
Gtk::Popover protection_info_popover_
Gtk::Label pid_value_
Gtk::Label rules_status_
BastionGuard::RootGuard::Client client_
void add_rule_from_entry(Gtk::Entry &entry, Gtk::ListBox &list)
Gtk::TextView events_view_
void add_installed_application_defaults()
std::unique_ptr< Gtk::Window > current_alert_
Gtk::Label protection_label_
Gtk::Label status_
std::set< std::string > dismissed_events_
void dismiss_event(const BastionGuard::RootGuard::Event &event)
void remove_selected_rule(Gtk::ListBox &list)
Gtk::ScrolledWindow incidents_scroller_
std::string event_fingerprint(const Event &event)
bool is_actionable_incident(const Event &event) noexcept
bool is_security_observation(const Event &event) noexcept
std::string format_event(const Event &event)
EventSeverity classify_event(const Event &event) noexcept
bool is_system_service_path(const std::string &path) noexcept
bool has_exact_path(const BastionGuard::RootGuard::Event &event)
Glib::ustring incident_detail(const BastionGuard::RootGuard::Event &event)
std::string incident_key(const BastionGuard::RootGuard::Event &event)
bool restore_available(const BastionGuard::RootGuard::Event &event)
Glib::ustring path_resolution_text(const BastionGuard::RootGuard::Event &event)
void append_path_row(Gtk::ListBox &list, const std::string &path)
Glib::ustring recovery_note(const BastionGuard::RootGuard::Event &event)
std::string rules_signature(const BastionGuard::RootGuard::ApplicationRules &rules)
bool remove_available(const BastionGuard::RootGuard::Event &event)
std::string trim_copy(std::string value)
Gtk::Box * make_status_item(const Glib::ustring &label, Gtk::Label &value)
bool closes_incident(const BastionGuard::RootGuard::Event &event)
Glib::ustring incident_title(const BastionGuard::RootGuard::Event &event)
@ RG_REASON_METADATA_BURST
std::vector< std::string > deniedExecutables
std::vector< std::string > trustedExecutables
std::vector< std::string > trustedApplications