1. Overview
The Privacy module provides runtime monitoring and access-control assistance for webcam and microphone usage. It detects processes accessing device nodes and prompts the user in real time with an explicit decision workflow (Allow / Deny).
The module implements the following behaviors:
- Automatic startup of the user service
BastionGuard-privacyd.service - Automatic device unblock at startup (no UI blocking)
- Real-time notifications when a new process accesses webcam/microphone devices
- Manual refresh and optional auto-refresh every 5 minutes
- Asynchronous logging to daily log files under the BastionGuard user data directory
- No UI blocking: system operations and scans are executed in background threads
2. User Interface Structure
2.1 Header
The page uses a security-themed header consistent with other modules.
- Container:
Gtk::Boxwith CSS classsecure-headerbar - Centered title label with CSS class
secure-header-title
Displayed title:
🔒 Privacy Webcam / Microphone
2.2 Privacy Switch
A central switch (switch_webcam_) enables or disables the privacy monitoring and device state. The initial switch state is loaded from local configuration using load_privacy_state().
Switch description label:
Enable/Disable Webcam and Microphone
When the switch changes, the handler on_toggle_privacy() is invoked.
2.3 Controls: Manual Refresh and Auto-Refresh
The module provides a control bar containing:
- Manual refresh button (
btn_refresh_) - Auto-refresh checkbox (
chk_auto_refresh_) with a 5-minute interval
Manual refresh triggers:
on_refresh_clicked()
Auto-refresh toggling triggers:
setup_auto_refresh_toggle()
2.4 Manual Device Unlock Button
A dedicated unlock action (btn_unlock_) is present but hidden by default. It becomes visible only when devices are considered blocked (for example after a “Deny” decision).
- Label:
🔓 Unlock devices - Behavior: runs the helper to unblock devices without blocking the UI
This action triggers:
manual_unlock_devices()
2.5 Process List and Daily Log Viewer
The central content area is a read-only, scrollable text view (txt_processes_ in scroller_). It is used to display:
- The current set of processes actively using webcam or microphone
- A separator line
- The daily log content for the current date
The view is:
- Read-only
- Word-wrapped
- Populated asynchronously
2.6 Status Label
A centered status label (lbl_status_) provides user feedback on initialization, refresh operations, toggle state changes, and device block/unblock results.
3. Initialization Flow
At page construction, the module performs the following initialization sequence:
- Loads the last privacy enabled/disabled state from
~/.config/BastionGuard/privacy.conf - Loads the auto-refresh preference (if present) from the same configuration file under the
privacygroup - Schedules an initial load of the current-day log file into the main viewer
- Schedules a background initialization task that:
- Ensures the privacy daemon is running
- Automatically unblocks devices locally
- Updates the process list
- Starts the monitoring thread
Daemon check/start is performed via systemd user services:
systemctl --user is-active --quiet BastionGuard-privacyd.service
systemctl --user start BastionGuard-privacyd.service
4. Device State Management
4.1 Automatic Unblock at Startup
On startup, the module attempts to unblock webcam and microphone devices locally using the helper:
/usr/bin/bastionguard-privhelper unblock
The result is logged, and the UI remains responsive as the operation is executed outside the GTK main thread.
4.2 Toggle Handling (Block / Unblock)
The privacy switch triggers the asynchronous execution of the helper:
/usr/bin/bastionguard-privhelper block
/usr/bin/bastionguard-privhelper unblock
Execution is performed in a background thread by run_privacy_script_async(unblock). On completion, UI updates are marshaled back to the GTK main loop using Glib::signal_idle().connect_once().
To prevent concurrent toggle execution, the module uses an atomic guard (toggle_busy_) and temporarily disables the switch during execution.
4.3 Manual Unlock
When devices are blocked, the module exposes a visible “Unlock devices” button that triggers manual_unlock_devices(). This executes:
/usr/bin/bastionguard-privhelper unblock
On success, the module hides the unlock button and updates internal state (devices_blocked_ = false).
5. Process Detection (Webcam / Microphone Usage)
Active access to webcam/microphone devices is detected by scanning the /proc filesystem and resolving open file descriptor symlinks.
5.1 Device Node Prefixes
The module considers device usage when a process holds file descriptors pointing to prefixes:
/dev/video(video devices / webcam)/dev/snd/pcm(PCM audio devices / microphone capture paths)
5.2 Detection Algorithm
detect_active_devices() performs:
- Iterates
/procdirectories, filtering numeric PIDs - Checks each process
/proc/<pid>/fddirectory (skipping permission errors) - Reads each FD symlink target and matches against device prefixes
- Resolves the process name from
/proc/<pid>/comm - Returns a unique sorted list of entries formatted as:
<process_name> (PID=<pid>)
6. Real-Time Monitoring Thread
The module runs a dedicated monitoring thread (monitor_thread_) controlled by an atomic flag (monitor_running_).
6.1 Polling Interval
The monitoring loop polls every 3 seconds:
std::this_thread::sleep_for(std::chrono::seconds(3))
6.2 New Access Detection
When a new process appears in the active device access list and is not already known, it is:
- Added to the internal set
known_processes_ - Logged to the daily log
- Queued for user confirmation via a permission popup
Popup creation is scheduled on the GTK main loop:
Glib::signal_idle().connect_once(...)
6.3 Removal Handling
When processes stop accessing devices, they are removed from known_processes_ to keep the state consistent.
7. Permission Popups (Sequential Decision Queue)
When new device access is detected, the module shows a permission prompt allowing the user to explicitly accept or deny access. Popups are displayed sequentially to prevent multiple simultaneous dialogs.
7.1 Queue Model
The module uses:
- A global queue
alert_queue_(process names) - A single active popup pointer
current_alert_
New alerts are enqueued by show_permission_popup(). If no popup is currently displayed, the next alert is shown via show_next_alert().
7.2 Popup Content and Controls
The popup displays:
- A warning title (“device access detected”)
- The process name attempting access
- A pending count if additional processes are queued
- Two explicit actions: Allow / Deny
7.3 Allow Behavior
Choosing Allow:
- Logs an “access allowed” entry
- Closes the popup and continues with the next queued alert
- Does not apply a device block operation
7.4 Deny Behavior (Block Devices)
Choosing Deny:
- Logs an “access denied” entry
- Marks devices as blocked (
devices_blocked_ = true) - Shows the manual unlock button in the main UI
- Executes the privileged block operation in a background thread:
/usr/bin/bastionguard-privhelper block
Once completed, the module updates the status label and refreshes the process list.
8. Manual Refresh and Auto-Refresh
8.1 Manual Refresh
Manual refresh is triggered by on_refresh_clicked(), which updates the status label, logs the action, and calls update_process_list().
8.2 Auto-Refresh
The auto-refresh option, when enabled, schedules a recurring timer every 300 seconds (5 minutes). The timer:
- Spawns a background thread to execute
update_process_list() - Continues running only while the checkbox remains active
The auto-refresh preference is persisted to:
~/.config/BastionGuard/privacy.conf
9. Process List Rendering and Daily Log Integration
update_process_list() runs asynchronously and builds a combined output containing:
- Current active device-using processes
- A visual separator
- The daily log content for the current date
Daily log files follow the naming convention:
privacyd_YYYY-MM-DD.log
and are stored under:
~/.local/share/BastionGuard/logs
UI updates are applied on the GTK main loop with Glib::signal_idle().connect_once().
10. Asynchronous Logging and Log Retention
Logging is performed via write_log(msg) using a dedicated background thread to avoid blocking the UI.
Each log entry includes a timestamp (HH:MM:SS) and is appended to the current-day log file.
The module also implements automatic retention cleanup: log files older than 7 days are removed from the log directory.
11. Settings Persistence
The module uses a GLib keyfile configuration stored at:
~/.config/BastionGuard/privacy.conf
Stored keys include:
[privacy] enabled– current privacy switch state[privacy] auto_refresh– auto-refresh enabled/disabled
State changes are persisted via:
save_privacy_state(enabled)setup_auto_refresh_toggle()
12. Threading and UI Safety
- All blocking operations (system calls, /proc scanning, file I/O, log writing) are executed in background threads.
- All UI updates are marshaled to the GTK main loop via
Glib::signal_idle()orGlib::signal_timeout(). - The monitoring thread lifecycle is controlled by an atomic flag and is safely joined on destruction.
- The toggle flow prevents concurrent operations by using an atomic guard (
toggle_busy_) and disabling the switch during execution.
13. Security Considerations
- Least UI privilege: privileged operations are delegated to a dedicated helper (
bastionguard-privhelper), keeping the UI process unprivileged. - Explicit consent model: new device access attempts require explicit user decision, presented sequentially to avoid dialog flooding.
- Controlled blocking: the “Deny” decision triggers device block actions and exposes a manual unlock path.
- Auditability: all important events (startup, access attempts, allow/deny, block/unblock, refresh) are recorded in daily logs.