276 : Gtk::Box(Gtk::Orientation::VERTICAL, 8)
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);
286 title_.set_text(
_(
"🛡️ RootGuard — Permission and Privilege Protection"));
288 title_.set_halign(Gtk::Align::CENTER);
290 title_.add_css_class(
"secure-header-title");
291 title_box->append(
title_);
294 auto* summary = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
295 summary->set_homogeneous(
true);
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_));
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);
319 auto* protection_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
320 protection_box->set_halign(Gtk::Align::CENTER);
323 _(
"Blocks protected system metadata changes before they are committed."));
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);
344 overview->append(*protection_box);
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);
352 overview->append(*scope);
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);
362 auto* controls = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
363 controls->set_halign(Gtk::Align::CENTER);
379 overview->append(*controls);
381 auto* overview_spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
382 overview_spacer->set_vexpand(
true);
383 overview->append(*overview_spacer);
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);
403 Gtk::PolicyType::AUTOMATIC);
406 page_stack_.add(*incidents_panel,
"incidents",
_(
"Incidents"));
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);
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");
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);
426 events_panel->append(*events_header);
442 Gtk::PolicyType::AUTOMATIC);
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);
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);
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);
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"));
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"));
497 _(
"Blocked executables"),
498 _(
"Executables blocked during RootGuard privilege-transition checks. This is not "
499 "a general application-launch blacklist."),
501 "denied-executables",
_(
"Blocked"));
507 auto* save_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
508 save_row->set_halign(Gtk::Align::END);
513 rules_page->append(*save_row);
519 _(
"Unsaved changes are never applied silently. RootGuard restarts only after a successful policy save."));
521 page_stack_.add(*rules_page,
"application-rules",
_(
"Application rules"));
525 status_.set_halign(Gtk::Align::CENTER);
557 add_defaults->signal_clicked().connect(
569 status_.set_text(
_(
"Loading RootGuard status and recent security events…"));
588 const Glib::ustring& description,
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);
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);
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);
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);
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);
631 add->signal_clicked().connect([
this, &entry, &list] {
634 entry.signal_activate().connect([
this, &entry, &list] {
637 remove->signal_clicked().connect([
this, &list] {
672 static const std::vector<std::string> candidates {
673 "/usr/bin/gnome-shell",
675 "/usr/bin/plasmashell",
676 "/usr/bin/kwin_wayland",
678 "/usr/bin/xfdesktop",
683 "/usr/bin/mate-panel",
686 "/usr/lib/firefox/firefox",
688 "/usr/bin/google-chrome-stable",
690 "/usr/bin/vivaldi-stable",
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))
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;
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)
724 _(
"No new installed desktop or browser defaults were found."));
729 _(
"Added %1 installed desktop/browser executable(s). Review and save to restart RootGuard."),
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));
919 if (closed_paths.count(incident_key(*iterator)) != 0 ||
920 displayed_paths.count(incident_key(*iterator)) != 0 ||
926 displayed_paths.insert(incident_key(*iterator));
927 incidents.push_back(*iterator);
928 if (incidents.size() >= 60)
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);
941 for (
const auto& event : incidents) {
942 auto* row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 8);
944 row->add_css_class(
"rootguard-incident-row");
946 auto* title = Gtk::make_managed<Gtk::Label>(incident_title(event));
947 title->set_halign(Gtk::Align::START);
949 if (event.verdict ==
"blocked")
950 title->add_css_class(
"rootguard-blocked");
952 title->add_css_class(
"rootguard-alert");
954 title->add_css_class(
"dim-label");
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);
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);
967 if (!can_restore && !can_remove) {
968 const auto note_text = audit_event
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");
981 auto* actions = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
982 actions->set_halign(Gtk::Align::END);
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);
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);
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);
1029 std::vector<BastionGuard::RootGuard::Event> new_events;
1030 for (
const auto& event : snapshot.
events) {
1031 if (event.verdict ==
"trusted")
1033 if (closes_incident(event) && !event.filename.empty()) {
1041 const std::string fingerprint =
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")
1054 (first_for_path || service_failure)) {
1055 new_events.push_back(event);
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);
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";
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;
1089 const bool cooldownExpired =
1092 if (burst != selected.end() && cooldownExpired)
1107 if (event.rule_id == 0 &&
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);
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"))
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);
1143 auto* content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
1144 content->set_margin(18);
1145 win->set_child(*content);
1147 const Glib::ustring message_template =
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(
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);
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);
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);
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);
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);
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
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);
1224 auto* buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
1225 buttons->set_halign(Gtk::Align::CENTER);
1227 auto* acknowledge = Gtk::make_managed<Gtk::Button>(
1228 event.verdict ==
"blocked" ?
_(
"Keep blocked") :
_(
"Acknowledge"));
1229 buttons->append(*acknowledge);
1232 auto* restore = Gtk::make_managed<Gtk::Button>(
_(
"Restore"));
1233 restore->add_css_class(
"btn-primary");
1234 restore->signal_clicked().connect([
this, event] {
1238 buttons->append(*restore);
1241 auto* remove = Gtk::make_managed<Gtk::Button>(
_(
"Quarantine"));
1242 remove->add_css_class(
"btn-danger");
1243 remove->signal_clicked().connect([
this, event] {
1247 buttons->append(*remove);
1249 content->append(*buttons);
1251 acknowledge->signal_clicked().connect([
this, event] {
1255 win->signal_close_request().connect([
this, event]() ->
bool {
std::optional< BastionGuard::RootGuard::Snapshot > last_snapshot_
void on_snapshot(const BastionGuard::RootGuard::Snapshot &snapshot)
void queue_new_alerts(const BastionGuard::RootGuard::Snapshot &snapshot)
void render_incidents(const BastionGuard::RootGuard::Snapshot &snapshot)
void remove_event(const BastionGuard::RootGuard::Event &event)
void dismiss_alert(const BastionGuard::RootGuard::Event &event)
std::vector< std::string > collect_rules(const Gtk::ListBox &list) const
void restore_event(const BastionGuard::RootGuard::Event &event)
void render_events(const BastionGuard::RootGuard::Snapshot &snapshot)
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)
void dismiss_event(const BastionGuard::RootGuard::Event &event)