Overview
The Scan module is implemented by the ScanPage class and provides both manual scanning and automatic (on-download / on-change) scanning. It integrates the ClamAV engine via clamdscan, and optionally performs a cloud reputation check using MalwareBazaar based on the SHA-256 hash of the scanned file.
The module is designed to operate asynchronously and avoid blocking the GTK main loop, using:
Gio::Subprocessfor ClamAV executionGio::DataInputStream::read_line_async()for non-blocking output parsinginotify+Glib::IOSourcefor filesystem monitoringGlib::signal_idle()andGlib::signal_timeout()for safe UI updates- A worker
std::threadfor cloud queries (no GTK calls outside the main thread)
User Interface Structure
Action Toolbar
The top of the page contains a button bar providing manual selection and scan controls:
btnChooseFolder→ triggersScanPage::onChooseFolder()btnChooseFile→ triggersScanPage::onChooseFile()btnScan→ triggersScanPage::onStartScan()
Automatic Scan Switch
An automatic scanning section is presented with a switch:
- Label:
autoScanLabel(text: “Scansione automatica”) - Control:
autoScanSwitch(default: active)
When the switch state changes, the handler:
autoScanSwitch.property_active().signal_changed()
performs the following actions:
- Updates the internal state flag
autoScanEnabled - Logs a state message (“automatic scan enabled/disabled”)
- If enabled: starts inotify monitoring via
enableAutoScanDownloads()andenableGlobalDownloadMonitor() - If disabled: stops monitoring via
disableAllMonitors() - Persists the configuration via
saveConfig()
Log View
The scan page includes a read-only log area implemented with:
Gtk::TextBuffer(textBuffer)Gtk::TextView(textView)Gtk::ScrolledWindow
File List View
A dedicated list area displays scanned files during manual scans using a Gtk::ListBox (fileListBox). Items are added via ScanPage::addFileToList() when ClamAV output indicates a file is being scanned.
Logging Model (Thread-Safe UI Logging)
Logging is buffered and flushed to the GTK UI in batches to avoid excessive UI updates and to ensure thread safety.
ScanPage::enqueueLog()pushes messages topendingLogprotected bylogMutex.- A delayed flush is scheduled once per burst using
Glib::signal_timeout().connect_once(..., 50). ScanPage::flushLog()drains the queue and appends messages to theGtk::TextBuffer.
Configuration Persistence
Local Scan Configuration
Automatic scan configuration is stored in:
~/.local/share/BastionGuard/config.json
The following key is persisted:
"auto_scan_enabled": true|false
Functions involved:
ScanPage::saveConfig()ScanPage::loadConfig()
Cloud Configuration (API Key)
Cloud reputation checks rely on a locally stored API key. The configuration file is:
~/.config/BastionGuard/cloud.conf
Key stored:
malware_bazaar_api_key=<value>
The module loads and applies the key using:
ScanPage::loadCloudConfig()(parsescloud.conf)ScanPage::saveCloudConfig()(writescloud.conf)- Updates the global variable
globalMalwareBazaarApiKey
Filesystem Monitoring (inotify)
Initialization
The scan page initializes inotify in non-blocking mode:
inotify_init1(IN_NONBLOCK | IN_CLOEXEC)
The file descriptor is integrated into the GTK main loop using:
Glib::IOSource::create(inotifyFd, Glib::IOCondition::IO_IN)
Events are processed in:
ScanPage::onInotifyEvent(Glib::IOCondition cond)
Recursive Watch Registration
Directory monitoring is set recursively using ScanPage::addInotifyRecursive(root), which:
- Normalizes paths (removes trailing slashes)
- Prevents duplicates via
alreadyWatched - Registers watches with:
IN_CREATE | IN_MOVED_TO | IN_CLOSE_WRITE | IN_ATTRIB
Subdirectories are iterated and hidden directories (name starting with .) are ignored.
Event Filtering and Debouncing
Events are filtered to reduce noise and avoid scanning incomplete downloads:
- Ignores noisy paths containing:
/build,/.cache,/.local,/.git - Accepts only:
IN_CLOSE_WRITEandIN_MOVED_TO - Skips temporary download files:
.part,.crdownload - Implements deduplication using a
recentlyScannedcache - Introduces a short stabilization delay before scanning (100ms)
All automatic scans are triggered via:
ScanPage::scanFileAutomatically(path)
Disabling Monitors
When automatic scanning is disabled, all monitors are disabled using ScanPage::disableAllMonitors(), which clears watch state and closes the inotify file descriptor.
Download Directory Detection
The module attempts to detect browser download directories to monitor likely incoming files. Detection is implemented in ScanPage::detectBrowserDownloadDirs() and includes:
- Default directory:
$HOME/Downloads - Firefox: reads
prefs.jsunder$HOME/.mozilla/firefox/<profile>/prefs.jsand extractsbrowser.download.dir - Chromium-based browsers: reads JSON Preferences from:
$HOME/.config/google-chrome/Default/Preferences
$HOME/.config/chromium/Default/Preferences
$HOME/.config/microsoft-edge/Default/Preferences
Duplicate directories are removed by sorting and applying std::unique.
Global Monitoring Scope
Beyond download directories, BastionGuard can enable broad monitoring via ScanPage::enableGlobalDownloadMonitor(). This includes:
$HOME/tmp$HOME/.var/app(if present)- XDG user directories parsed from:
$HOME/.config/user-dirs.dirs
XDG entries are resolved by expanding $HOME in paths and validating existence before monitoring.
Manual Scanning (clamdscan)
Scan Start Conditions
Manual scanning is initiated via ScanPage::onStartScan(). The function enforces:
- Only one scan at a time (
scanInProgress) - A selected target path must be set (
selectedPath)
ClamAV Invocation
The scan is executed using:
clamdscan --fdpass --no-summary --infected <selectedPath>
Output is captured using Gio::Subprocess with stdout/stderr pipes, and parsed asynchronously line-by-line.
Output Parsing
In ScanPage::readOutput():
- Lines beginning with
Scanningcause the scanned file path to be added to the list viaaddFileToList() - Lines containing
FOUNDare parsed to extract:
<filepath>: <virusName> FOUND
Detected infections are recorded in infectedFilesBuffer and handled through handleInfectedFileWithName(filepath, virusName).
Automatic Scanning (File Integrity Checks)
Automatic scanning is triggered when filesystem events indicate a completed file write or file move. The scan routine includes defensive checks:
- Rejects non-regular files (directories, symlinks, pipes, sockets) using
std::filesystem::is_regular_file() - Rejects extremely small files (less than 64 bytes) to avoid scanning incomplete downloads
- Deduplicates scanning using a composite key of:
<filepath>_<size>_<last_write_time>
Automatic ClamAV scans use the same invocation pattern:
clamdscan --fdpass --no-summary --infected <filepath>
Cloud Reputation Check (MalwareBazaar)
Trigger Conditions
After an automatic scan, if ClamAV does not report an infection, the module performs a cloud reputation lookup via cloudCheckMalwareBazaar(filepath).
Privacy-Preserving Model
The design explicitly avoids uploading files. Instead:
- BastionGuard computes the SHA-256 hash of the file locally
- Only the hash value is compared against public threat intelligence databases
- No local file contents are transmitted
Worker Thread Execution
The cloud lookup is executed in a detached worker thread:
std::thread(&ScanPage::cloudCheckMalwareBazaarWorker, this, filepath).detach();
The worker computes:
MalwareBazaar::sha256_file(filepath)
and then performs:
MalwareBazaar::lookupHash(sha)
UI updates are marshaled back into the GTK main loop using:
Glib::signal_idle().connect_once(...)
Cloud Detection Handling
If a hash match is found, the UI logs details (name, file type, tags) and triggers an alert through D-Bus when available (see next section). If no match is found, the module logs a “no match / safe” status.
Threat Alerts (D-Bus + GUI Fallback)
When malware is detected (via ClamAV or cloud reputation), the module attempts to raise an alert using a session D-Bus interface:
org.BastionGuard.Ransomware.Alert
/org/BastionGuard/ransomware/alert
org.BastionGuard.Ransomware.Alert
Method invoked:
ShowAlert(filepath, familyOrName)
If D-Bus is unavailable or errors occur, the module falls back to a local Gtk::MessageDialog warning dialog.
Infected files are handled through:
handleInfectedFile(filepath)→ uses familyClamAV.DetectedhandleInfectedFiles(files)→ bulk alertshandleInfectedFileWithName(filepath, virusName)→ exact detection name
AUR PKGBUILD Detection and Scanning
The Scan module includes a specific workflow for Arch Linux AUR build scripts (PKGBUILD) via:
ScanPage::onAurPkgbuildDetected(path)
Behavior includes:
- Special marker handling:
INFECTED:<path>triggers a direct infection workflow using familyAUR.StaticHeuristic - Static heuristic analysis via:
AurScan::scanPKGBUILD(path)
and an additional ClamAV scan via:
ScanPage::scanAurAuto(path)
Cloud Privacy Disclaimer Window
The module provides a dedicated informational UI explaining cloud privacy rules. It is opened via:
ScanPage::showCloudPrivacyDisclaimer()
This window states that BastionGuard:
- Does not upload files
- Computes only the SHA-256 hash locally
- Performs a reputation lookup using public databases
- Does not transmit local paths or personal data
- Stores the API key locally only
The disclaimer window uses custom CSS and a custom header bar. It is non-modal and can be closed via the custom close button or the “I understand” button.
Security and Robustness Controls
- Non-blocking design: scanning and I/O are handled asynchronously to keep the UI responsive.
- Inotify noise reduction: ignores cache/build directories and temporary download artifacts.
- File safety gating: only regular files are scanned; extremely small files are skipped.
- Deduplication: prevents repeated scanning bursts on the same file events.
- Safe error handling: configuration and file operations use try/catch blocks to prevent crashes.
- Thread safety: cloud worker threads do not access GTK directly; UI updates are marshaled to the main loop.
Key Files and Paths
- Auto-scan config:
~/.local/share/BastionGuard/config.json - Cloud config:
~/.config/BastionGuard/cloud.conf - XDG dirs source:
~/.config/user-dirs.dirs - Manual/Auto scan engine:
clamdscan - D-Bus alert interface:
org.BastionGuard.Ransomware.Alert