MALDET(1)

NAME
maldet - Linux malware scanner for shared hosting environments

SYNOPSIS
maldet [OPTIONS] COMMAND [ARGS]

DESCRIPTION
maldet (Linux Malware Detect) is an open-source malware scanner designed for Linux shared hosting environments. It provides multi-stage detection using MD5 and SHA-256 hashes, HEX patterns, compound signatures (csig), statistical analysis, native YARA rules, and optional ClamAV integration. Detected threats can be quarantined, cleaned, or reported via email, Slack, Telegram, and Discord.

OPTIONS

  Scanning

-a, --scan-all PATH
    Scan all files in PATH (default: /home).

-r, --scan-recent PATH DAYS
    Scan files created or modified in the last DAYS days (default: 7).

-f, --file-list FILE
    Scan files listed in FILE, one path per line.

-b, --background
    Run the scan operation in the background. May appear anywhere in the command line.

  Filters

-i, --include-regex REGEX
    Include only paths matching POSIX extended regular expression. May appear anywhere in the command line.

-x, --exclude-regex REGEX
    Exclude paths matching POSIX extended regular expression. May appear anywhere in the command line.

  Monitoring

-m, --monitor USERS|PATHS|FILE|RELOAD
    Start inotify real-time monitoring. By default, the supervisor runs in the foreground and holds the terminal. Use -b before --monitor to daemonize: maldet -b -m users. users monitors homedirs of UIDs above inotify_minuid. PATHS accepts a comma-separated list. FILE reads paths from a file. reload triggers a configuration reload.

-k, --kill-monitor (also: -kill)
    Stop the inotify monitoring service. Sends SIGTERM to the supervisor for a clean shutdown; escalates to SIGKILL after a 10-second timeout if the process does not exit.

  Quarantine and Restore

-q, --quarantine SCANID
    Quarantine all hits from scan report SCANID.

-n, --clean SCANID
    Attempt to clean malware from files in scan report SCANID.

-s, --restore FILE|SCANID
    Restore a quarantined file or all files from SCANID.

-qd PATH
    Override the quarantine directory for this run. PATH must exist or the command exits with an error. May appear anywhere in the command line.

  Reporting

-e, --report [SCANID|list|latest|hooks|active]
    View a scan report. Use latest for the most recent scan, list for all reports, hooks for hook scan activity (see HOOK SCANNING), active for running scans (equivalent to -L). Combine with --format and --mailto for output control.

--all
    Show full scan history when used with -e list. Without --all, -e list shows only recent sessions. May appear anywhere in the command line.

--verbose
    Show additional detail with -L (workers, signature version, progress). May appear anywhere in the command line.

--format text|json|html|tsv
    Set report output format for -e/--report and -L (default: text). May appear anywhere in the command line.

--mailto ADDRESS
    Email the report to the specified address instead of stdout. May appear anywhere in the command line.

--json-report [SCANID|list|newest]
    Shorthand for --report --format json. Output a JSON-formatted scan report for the given SCANID (or the most recent scan when newest is given). Use list to output all scan reports as a JSON array. Both endpoints emit JSON schema 1.2 (uniform top-level shape: schema_version, scanner, host, reports[]). See the JSON OUTPUT section below for the per-scan shape.

--alert-daily (also: --monitor-report)
    Generate a digest alert summarizing inotify monitor activity. Requires monitor mode to be running.

-l, --log
    Display the event log.

  JSON Output
All JSON-emitting commands (--json-report, --format json -e, --format json -L) produce schema 1.2 with a uniform top-level shape:

    {
      "schema_version": "1.2",
      "scanner": { "name", "version", "engine", "hash_type", "sig_version" },
      "host":    { "hostname", "host_id" },
      "reports": [ ... ]
    }

For --json-report SCANID, reports contains a single object with the scan record, hit list, and summary. For --json-report list, reports contains all completed scans (newest-first by started_epoch), and the JSON document also carries active[] and stopped[] arrays for in-flight scans (running, paused, stale) and resumable checkpointed scans.

Each report object carries: scan_id, path, started (string), started_epoch (integer), completed (string), completed_epoch (integer), elapsed_seconds, total_files, total_hits, total_cleaned, total_quarantined, quarantine_enabled (boolean), and — on the per-scan endpoint — hits[] and summary{}.

Legacy plaintext sessions (pre-v2.0.1) render with reports[0].source = "legacy" and emit null for fields not recoverable from the plaintext format (per-hit hash/size/owner/group/mode/mtime; per-scan scanner.{engine, hash_type, version, sig_version}; reports[].{completed, engine, hash_type}).

  Updates

-u, --update-sigs (also: --update) [--force]
    Update malware signatures from rfxn.com.

-d, --update-ver (also: --update-version) [--force|--beta]
    Update the LMD installation to the latest version.

--cron-sigup
    Perform a signature update for the independent cron job. Serialized with flock to prevent concurrent runs. Includes a random 0-120s sleep to avoid upstream stampede. Internal use only — invoked by /etc/cron.d/maldet-sigup; not intended for direct invocation.

  Other

-c, --checkout FILE
    Upload a suspected malware sample to rfxn.com for analysis.

--mkpubpaths
    Create per-user pub/ data directories for non-root scan operations. Requires scan_user_access=1 in conf.maldet.

-p, --purge
    Clear logs, quarantine queue, session, and temporary data.

-U, --user USER
    Run as the specified user (for viewing reports or restoring quarantine).

-co, --config-option VAR=VAL[,VAR=VAL,...]
    Override configuration options at runtime. May appear anywhere in the command line.

--web-proxy (also: --wget-proxy, --curl-proxy) IP:PORT
    Set HTTP/HTTPS proxy for all remote connections.

-hscan, --hook-scan (also: --modsec)
    Scan a single file supplied by a service hook (ModSecurity, pure-ftpd, ProFTPD, Exim, or custom). Used internally by hookscan.sh; not intended for direct invocation. May appear anywhere in the command line.

--digest
    Fire a unified digest alert reading all available sources: monitor session hits, hook scan hits, cleaned/suspended history. Does not require monitor mode — fires if any source has new data since the last digest. Cursors advance on read, preventing duplicate alerts.

--test-alert TYPE CHANNEL
    Send a test alert with synthetic data to verify alerting configuration. TYPE: scan (per-scan alert with 3 synthetic hits) or digest (periodic summary). CHANNEL: email, slack, telegram, or discord. Uses the real rendering pipeline with a [TEST] subject prefix. Channel isolation ensures only the specified channel receives the alert. Requires root privileges.

-v, --version
    Display version information.

-h, --help
    Display usage information.

EXIT STATUS

0   Success; no malware found.

1   Error or invalid arguments; or all provided scan paths do not exist.

2   Malware hits detected.

FILES

/usr/local/maldetect/conf.maldet
    User configuration file.

/usr/local/maldetect/internals/internals.conf
    Internal paths, binary discovery, and URL definitions.

/usr/local/maldetect/internals/compat.conf
    Backward compatibility variable mappings.

/usr/local/maldetect/internals/lmd.lib.sh
    Core library sourcing hub — sources all sub-libraries in dependency order.

/usr/local/maldetect/internals/lmd_*.sh
    Decomposed function libraries: lmd_config, lmd_init, lmd_clamav, lmd_sigs, lmd_engine, lmd_yara, lmd_quarantine, lmd_session, lmd_scan, lmd_monitor, lmd_update, lmd_alert.

/usr/local/maldetect/internals/alert_lib.sh
    Shared alerting library (email, Slack, Telegram, Discord delivery).

/usr/local/maldetect/internals/lmd_alert.sh
    LMD-specific alert functions (data preparation, rendering pipeline).

/usr/local/maldetect/internals/alert/
    Alert template directory (HTML, text, and messaging templates). Custom overrides go in alert/custom.d/ and are preserved across upgrades.

/usr/local/maldetect/internals/tlog_lib.sh
    Shared incremental log reading library.

/usr/local/maldetect/internals/elog_lib.sh
    Shared structured event logging library.

/usr/local/maldetect/internals/pkg_lib.sh
    Shared packaging utility library (RPM/DEB manifest helpers).

/usr/local/maldetect/sigs/
    Signature directory (MD5, SHA-256, HEX, CSIG, YARA, ClamAV).

/usr/local/maldetect/sigs/csig.dat
    Compound signature rules (multi-pattern boolean logic).

/usr/local/maldetect/sigs/custom.csig.dat
    User-defined compound signature rules.

/usr/local/maldetect/sigs/sha256v2.dat
    SHA-256 hash signatures (HASH:SIZE:{SHA256}sig.name.N).

/usr/local/maldetect/sigs/custom.sha256.dat
    User-defined SHA-256 hash signatures.

/usr/local/maldetect/sigs/rfxn.hsb
    ClamAV SHA-256 hash signature database (ClamAV >= 0.97).

/usr/local/maldetect/conf.maldet.hookscan
    Hook scan configuration (user-customizable, preserved across upgrades).

/usr/local/maldetect/conf.maldet.hookscan.default
    Reference hook scan configuration with default values and documentation.

/usr/local/maldetect/sess/hook.hits.log
    Rolling hook scan detection log (TSV format, append-only). Each line is an 11-field hit record plus hook mode and timestamp. Read by digest alerts and --report hooks.

/usr/local/maldetect/quarantine/
    Quarantine storage for detected malware.

/usr/local/maldetect/sess/session.tsv.*
    TSV-format scan session data files containing a 19-field metadata header and 11-field hit records. Canonical format as of v2.0.1.

/var/log/maldet/
    Log directory (FHS). Contains event_log, clamscan_log, inotify_log, and audit.log. /usr/local/maldetect/logs is a backward-compatible symlink to this directory.

/etc/logrotate.d/maldet
    Logrotate configuration for event_log, clamscan_log, and audit.log. Installed only when logrotate(8) is available. Does not rotate inotify_log (managed internally by --maintenance).

/etc/cron.daily/maldet
    Daily cron job for automated scanning and updates.

/etc/cron.weekly/maldet-watchdog
    Weekly watchdog for fallback signature updates.

/etc/cron.d/maldet-sigup
    Independent signature update cron job (interval set by sigup_interval).

/etc/sysconfig/maldet  or  /etc/default/maldet
    System-level configuration overrides.

/usr/local/maldetect/monitor_paths.extra
    Line-separated file of additional paths to add to the inotify watch list. Managed independently of conf.maldet. See monitor_paths_extra in the CONFIGURATION section.

$sessdir/scan.meta.$scanid
    Scan lifecycle metadata. Contains process info, state, engine type, scan options, and timestamps. Updated throughout the scan lifecycle as state transitions occur.

$sessdir/session.index
    Append-only session index for O(1) scan listing. Header line #LMD_INDEX:v2 (schema 1.2 — prior installs may show v1). Each data line is a 14-field tab-separated record (scanid, epoch, started_hr, elapsed, total_files, total_hits, total_cleaned, total_quarantined, path, sig_version, quarantine_enabled, completed_hr, engine, hash_type). Readers tolerate older 8/9/11-field rows from prior schema versions. Used by --report list and -L for fast enumeration without scanning the session directory.

$sessdir/scan.checkpoint.$scanid
    Stage checkpoint file for --continue. Written by --stop with the #LMD_CHECKPOINT:v1 format. Contains the last completed stage, accumulated hits, config options, and signature version at the time of stop.

$tmpdir/.abort.$scanid
    Abort sentinel. Created by --kill or --stop. Workers check for this file at stage boundaries and during scan loops for cooperative shutdown.

$tmpdir/.pause.$scanid
    Pause sentinel with epoch timestamp and optional duration. Created by --pause. Workers enter a sleep loop while this file exists. Removed by --unpause or when the duration expires.

$sessdir/session.archive.YYMM.tsv.gz
    Monthly session archive created by --maintenance. Consolidates all session TSV files from the given month (YYMM) into a single gzip-compressed archive. The --report command transparently resolves sessions from archives when the original session file has been archived.

CONFIGURATION
Configuration is loaded from conf.maldet and can be overridden by system files and the -co flag. Key variables by category:

Alerting: email_alert, email_addr, email_ignore_clean, email_subj, email_panel_user_alerts, email_panel_from, email_panel_replyto, email_panel_alert_subj, email_format, smtp_relay, smtp_from, smtp_user, smtp_pass, slack_alert, slack_subj, slack_token, slack_channels, telegram_alert, telegram_bot_token, telegram_channel_id, discord_alert, discord_webhook_url.

Scanning: scan_hashtype, scan_clamscan, scan_csig, scan_yara, scan_yara_timeout, scan_yara_scope, scan_workers, scan_user_access, scan_user_access_minuid, scan_max_depth, scan_min_filesize, scan_max_filesize, scan_hexdepth, scan_hex_chunk_size, scan_find_timeout, scan_export_filelist, scan_tmpdir_paths, scan_cpunice, scan_ionice, scan_cpulimit, scan_ignore_root, scan_ignore_user, scan_ignore_group, string_length_scan, string_length, scan_progress_log_interval.

Quarantine: quarantine_hits, quarantine_clean, quarantine_suspend_user, quarantine_suspend_user_minuid, quarantine_on_error.

Monitoring: default_monitor_mode, inotify_base_watches, inotify_minuid, inotify_docroot, inotify_sleep, inotify_reloadtime, inotify_cpunice, inotify_ionice, inotify_cpulimit, digest_interval, digest_escalate_hits, monitor_paths_extra, monitor_scan_owner_filters.

Hook Scanning: hookscan_timeout, hookscan_fail_open, hookscan_escalate_hits, hookscan_service_users, hookscan_user_rate_limit, hookscan_user_show_signames, hookscan_list_max_bytes, hookscan_list_max_entries, cron_digest_hook.

Remote ClamAV: scan_clamd_remote, remote_clamd_config, remote_clamd_max_retry, remote_clamd_retry_sleep.

ELK Integration: enable_statistic, elk_host, elk_port, elk_index.

Maintenance: autoupdate_signatures, autoupdate_version, autoupdate_version_hashed, sigup_interval, cron_prune_days, cron_daily_scan, scan_days, import_config_url, import_config_expire, session_legacy_compat.

Signature Import: sig_import_md5_url, sig_import_hex_url, sig_import_yara_url, sig_import_sha256_url, sig_import_csig_url.

autoupdate_version_hashed validates the SHA-256 hash of installed LMD executables against known good upstream values on each update check. Falls back to MD5 verification if sha256sum is unavailable. Forces reinstallation on mismatch. Default: 1.

sigup_interval sets the interval in hours for automatic signature update checks via the independent cron job (/etc/cron.d/maldet-sigup). This runs separately from cron.daily and is idempotent with maldet -u. Set to 0 to disable the independent cron job. Valid range: 1-24. Default: 6.

scan_hashtype selects the hash algorithm for file-hash signature matching (stage 1). Values: auto (use SHA-256 if CPU has hardware acceleration, else MD5), sha256 (always SHA-256), md5 (always MD5; preserves performance on older CPUs without SHA-NI), both (run both MD5 and SHA-256 stages for maximum coverage). Default: auto.

scan_clamscan enables ClamAV as the scan engine for improved performance on large file sets. When set to auto (default), LMD detects the clamscan binary at runtime and enables ClamAV scanning only if available. Values: auto (detect binary), 0 (disabled), 1 (enabled). Default: auto.

scan_yara enables native YARA scanning via the yara binary (or yr from YARA-X). When set to auto (default), LMD enables native YARA scanning only when ClamAV is unavailable and a yara/yr binary is found. This prevents duplicate YARA rule evaluation since ClamAV already processes YARA rules internally. To force native YARA alongside ClamAV, set to 1 and use scan_yara_scope to control which rules each engine handles. Values: auto (detect binary, ClamAV fallback), 0 (disabled), 1 (enabled). Default: auto.

scan_csig enables compound signature (csig) scanning. Compound signatures support multi-pattern boolean logic (AND/OR/threshold), case-insensitive matching, wide (UTF-16LE) matching, and bounded gap wildcards. Runs in native mode only (not with ClamAV). Default: 1.

scan_workers sets the number of parallel workers for native MD5, SHA-256, HEX, and CSIG scan passes. A value of auto (default) uses automatic detection: 2 workers per CPU core, up to 8. Set to 1 for single-threaded operation. Maximum value: 8.

scan_find_timeout sets the maximum execution time in seconds for the find(1) file list generation phase. A value of 0 disables the timeout. Values between 1 and 59 are silently ignored (minimum is 60). Default: 0.

scan_progress_log_interval sets the interval in seconds between progress checkpoints written to the event log during background scans (maldet -b). Each checkpoint reports the current scan stage, files processed, percentage complete, elapsed time, and estimated time remaining. A value of 0 disables progress logging. Default: 60.

scan_hexdepth sets the maximum byte depth that the scanner reads from each file during HEX signature matching (stage 2). The default (262144 = 256 KB) covers 98.9% of HEX patterns; the remaining 1.1% of deep patterns (php.inject.* at offsets > 256 KB) are caught by YARA stage 3 when scan_yara is enabled (default). Higher values increase temporary disk usage during large scans. Default: 262144.

scan_hex_chunk_size sets the maximum number of files processed per micro-batch during HEX+CSIG scanning. Lower values reduce temporary disk usage; higher values may improve throughput on fast storage. Valid range: 1024–20480. Values outside this range are clamped with a logged warning. To increase throughput, raise scan_workers instead. To reduce disk usage, lower scan_hexdepth instead. Default: 10240.

scan_days sets the number of days to look back for recently modified files during daily cron scans. This value is passed as the DAYS argument to maldet -r. Default: 1.

email_format sets the format for email alerts. Values: text (plain text only), html (HTML with text fallback), both (multipart alternative with both formats). Default: html.

smtp_relay sets an SMTP relay for email delivery instead of the local MTA. When set, emails are sent via this relay using curl. Examples: smtps://smtp.gmail.com:465, smtp://relay.example.com:587. Requires smtp_from to be set.

smtp_from sets the sender address for SMTP relay delivery. Required when smtp_relay is set.

smtp_user and smtp_pass set SMTP authentication credentials for relay delivery.

session_legacy_compat controls generation of legacy plaintext session files alongside TSV. Values: auto (detect old-format sessions, enable if found — default), 1 (always generate plaintext), 0 (TSV only). Default: auto.

ClamAV Signature Validation

When ClamAV is installed, LMD validates signature files via clamscan -d against a staging directory before deploying them to ClamAV data directories. If validation fails, existing LMD signatures are removed from the ClamAV path to protect ClamAV from loading malformed databases. The SIGUSR2 reload signal to clamd is only sent when at least one ClamAV data directory passes validation. This prevents malformed signature updates from breaking ClamAV (issue #467).

digest_interval sets the period between automatic digest summary alerts in monitor mode. Accepts a numeric value with a unit suffix: Nh (hours), Nm (minutes), Nd (days). Set to 0 to disable periodic digests. Default: 24h.

digest_escalate_hits sets a hit-count threshold for immediate escalation alerts in monitor mode. When the number of new hits detected since the last digest exceeds this value, an alert is sent immediately rather than waiting for the next digest interval. Set to 0 to disable escalation. Default: 0.

monitor_paths_extra is the path to a line-separated file of additional paths to add to the inotify watch list. Entries in this file are merged with the primary watch list on every configuration reload, allowing paths to be added without modifying conf.maldet or restarting the monitor. Default: /usr/local/maldetect/monitor_paths.extra.

cron_digest_hook enables the daily cron job hook digest sweep. When enabled, cron.daily checks for new hook scan detections and fires a digest alert if any exist since the last sweep. Set to 0 to disable (e.g., if monitor mode handles hook digests). Default: 1.

hookscan_timeout sets the scan timeout in seconds for hook-triggered scans. ModSecurity has no built-in timeout — this prevents Apache worker thread exhaustion. pure-ftpd runs hook scripts sequentially — this prevents queue blocking. Default: 30.

hookscan_fail_open controls behavior when the scan subprocess exits with an unexpected error. When set to 1 (default), scanner errors allow the file through (fail-open). When set to 0, scanner errors block the file (fail-closed). Fail-open is the safe default: a broken scanner should not cause a site-wide upload outage. Default: 1.

hookscan_escalate_hits sets a hit-count threshold for immediate hook scan escalation alerts. When the number of hook detections within a 1-hour rolling window reaches this value, an immediate per-scan alert is fired. Set to 0 to disable escalation. Default: 0.

hookscan_service_users is a comma-separated list of service account usernames exempt from the non-root homedir restriction. Default: apache,nginx,www-data,nobody,proftpd,exim.

hookscan_user_rate_limit sets the maximum scans per hour for non-root callers. Hook-triggered scans (ModSecurity, FTP) from service UIDs are exempt. Set to 0 for unlimited. Default: 60.

hookscan_user_show_signames controls signature name visibility for non-root callers. When set to 0, non-root users see opaque “MALWARE-DETECTED” in output while full signature names are logged. When set to 1 (default), full signature names appear in all output. Default: 1.

hookscan_list_max_bytes sets the maximum file list size in bytes for batch scanning via --list. Default: 1048576 (1 MB).

hookscan_list_max_entries sets the maximum number of entries accepted from a file list or stdin pipe. Default: 10000.

Post-Scan Hooks: post_scan_hook, post_scan_hook_format, post_scan_hook_exec, post_scan_hook_timeout, post_scan_hook_on, post_scan_hook_min_hits.

post_scan_hook is the path to an executable script invoked after scan completion. The hook receives scan results via positional arguments ($1=SCANID, $2=HITS, $3=FILES, $4=EXIT_CODE, $5=SCAN_TYPE, $6=PATH) and LMD_* environment variables. The script must be owned by root and not world-writable. Leave empty to disable. This variable cannot be overridden via -co for security. Default: "" (disabled).

post_scan_hook_format controls the output tier (each tier includes all previous tiers): args (positional args + env only), file (adds LMD_SESSION_FILE), or json (adds JSON blob on stdin). Default: args.

post_scan_hook_exec controls execution mode: async (fork, non-blocking) or sync (wait for completion). Monitor mode always forces async regardless of this setting. Default: async.

post_scan_hook_timeout sets the timeout in seconds before the hook is sent SIGTERM via timeout(1). A well-behaved hook will exit on SIGTERM; a hook that traps and ignores SIGTERM will continue to run until it completes (no SIGKILL escalation). Set to 0 to disable. Values 1-4 are clamped to 5. Default: 60.

post_scan_hook_on filters which scan types trigger the hook: all, cli, or digest. Default: all.

post_scan_hook_min_hits sets the minimum malware hit count to trigger the hook. Set to 0 to fire on every scan including clean ones. Default: 1.

Scan Lifecycle: scan_progress_log_interval, scan_meta_cleanup_age, maint_compress_age, maint_archive_age.

scan_meta_cleanup_age sets the number of hours to retain completed/killed/stale scan.meta files before cleanup. Meta files for active/paused/stopped scans are never cleaned. Set 0 to disable automatic cleanup. Default: 48.

maint_compress_age sets the number of days before completed session files are compressed (gzipped). Sessions younger than this age are never compressed. Set 0 to disable automatic compression. Default: 30.

maint_archive_age sets the number of days before compressed session files are bundled into monthly archives. Sessions younger than this age are never archived. Set 0 to disable automatic archival. Default: 90.

See conf.maldet for the full list with descriptions.

MONITOR MODE
The monitor command runs a supervisor process that manages an inotifywait(1) child watching for file create, modify, and move events.

By default, maldet -m users runs in the foreground, keeping the terminal occupied. Use maldet -b -m users to daemonize the supervisor and return immediately. The -b flag may appear anywhere on the command line.

The supervisor automatically recovers from inotifywait crashes using exponential backoff (2, 4, 8, 16, 32 seconds). After three consecutive failures the supervisor exits with a logged error.

maldet -k sends SIGTERM to the running supervisor for a clean shutdown. If the process does not exit within 10 seconds, SIGKILL is sent.

A periodic digest summary alert is sent every digest_interval (default: 24h). If the number of new hits since the last digest exceeds digest_escalate_hits (default: 0, disabled), an immediate escalation alert is sent regardless of the interval.

Additional watch paths can be supplied without modifying conf.maldet by listing them (one per line) in the file pointed to by monitor_paths_extra. These are merged with the primary path list on every cycle reload.

By default, monitor mode scans every file that lands in its watched scope regardless of owner — including root-owned files. This restores the behavior of LMD 1.6.6 and closes the detection gap for root-owned malware dropped into user docroots (issue #485). The ownership filters scan_ignore_root, scan_ignore_user, and scan_ignore_group are NOT applied in monitor mode unless monitor_scan_owner_filters is set to 1, which re-enables the rc1–rc3 behavior for operators who have deliberately tuned their ownership filter configuration.

Monitor exclusions union two files: ignore_inotify (user-owned, preserved across upgrades) and ignore_inotify.defaults (LMD-managed under $inspath/internals/, refreshed on every upgrade). Entries in both files are line-separated and support # comments plus blank lines. The user-owned ignore_inotify accepts POSIX ERE (extended regular expressions) by default — anchors (^, $) and wildcards (.*, .+) work as expected. To exclude a literal path containing regex metacharacters, prefix the entry with literal:. The LMD-managed ignore_inotify.defaults is treated as literal substrings — its curated entries are auto-escaped at load time. The defaults file ships curated exclusions for systemd-private tmpdirs, MariaDB temp tables, PostgreSQL, Redis, ClamAV runtime, and common backup agents. Add site-specific exclusions to ignore_inotify only.

HOOK SCANNING
hookscan.sh provides real-time file scanning for service integrations. It supports multiple output modes for different services:

hookscan.sh [mode] file

Modes:

modsec
    ModSecurity @inspectFile (default if no mode specified). Stdout: 1 clean, 0 maldet: SIGNAME PATH infected.

ftp pure-ftpd pure-uploadscript daemon. Fire-and-forget: quarantines on detect, no stdout needed. Auto-detected when the UPLOAD_VUSER environment variable is set.

proftpd
    ProFTPD mod_exec. Fire-and-forget: quarantines on detect, no stdout needed.

exim
    Exim cmdline av_scanner. Stdout: maldet: clean or maldet: SIGNAME.

generic
    Machine-parseable output for custom integrations. Exit codes: 0 clean, 1 error, 2 infected. Stdout: CLEAN: PATH, INFECTED: SIGNAME PATH, or ERROR: reason. Accepts --list FILE or --stdin for batch file scanning.

Hook scan detections are logged to a rolling hit log (hook.hits.log) and included in periodic digest alerts. Individual hook scans do not create session files or fire per-scan alerts.

When the escalation threshold hookscan_escalate_hits is exceeded (N detections within a 1-hour window), an immediate per-scan alert is fired regardless of digest settings.

Configuration options for hook scanning are stored in conf.maldet.hookscan. The reference defaults are in conf.maldet.hookscan.default.

Backward compatibility: hookscan.sh /path/to/file (no mode argument) behaves identically to prior versions — ModSecurity mode.

  Batch File Lists (generic mode)

hookscan.sh generic --list FILE
    Scan files listed in FILE (one absolute path per line). Each line is validated: absolute path required, no shell metacharacters, no path traversal, no non-printable characters. Invalid lines are skipped with a syslog warning.

hookscan.sh generic --stdin
    Read file paths from stdin (pipe). Same per-line validation as --list. Terminal input is rejected.

Batch output produces one STATUS: PATH line per file. Exit code is worst-result-wins: 2 if any infected, 1 if any errors, 0 if all clean.

  Usermode Restrictions
Non-root callers are restricted to scanning files within their home directory. Service UIDs listed in hookscan_service_users are exempt from this restriction. Non-root callers are subject to rate limiting via hookscan_user_rate_limit.

TEST ALERTS
maldet --test-alert TYPE CHANNEL sends a synthetic test alert through the full rendering pipeline to verify that a delivery channel is correctly configured.

TYPE selects the alert template:

scan
    Per-scan alert with 3 synthetic hits (MD5, HEX, and YARA detection types). Exercises the hit-type color registry and per-type template rendering.

digest
    Periodic summary alert with synthetic data spanning all 5 digest sources.

CHANNEL selects the delivery target: email, slack, telegram, or discord.

Channel isolation ensures only the specified channel receives the test alert; all other channels are temporarily suppressed. If the target channel is not enabled in conf.maldet, an error is reported.

The subject line is prefixed with [TEST] to distinguish test alerts from real detections.

Requires root privileges.

Examples:

Verify email alerting:
    maldet --test-alert scan email

Verify Slack digest delivery:
    maldet --test-alert digest slack

DIGEST ALERTS
Digest alerts provide periodic summaries of detection activity from all sources: inotify monitor hits, hook scan detections, cleaned files, and suspended users.

Three trigger paths fire digest alerts:

Monitor timer
    When monitor mode is running, a digest fires every digest_interval (default: 24h).

cron.daily
    When cron_digest_hook=1 (default), the daily cron job fires a digest if new hook scan detections exist since the last sweep.

--digest CLI
    maldet --digest fires an on-demand digest reading all 5 tlog-cursored sources. Cursors advance on read, so subsequent digests see only new data.

When monitor mode is not running, digest alerts can still fire via cron.daily or --digest if hook scan detections exist. The monitor-specific sections of the digest template render as empty when monitor mode is inactive.

  Hook Scan Reports

maldet --report hooks
    Display all hook scan detections from the rolling hit log (default: last 24h).

maldet --report hooks --last DURATION
    Filter by time: Nh (hours), Nd (days), Nm (minutes).

maldet --report hooks --mode MODE
    Filter by hook mode: modsec, ftp, proftpd, exim, or generic.

SCAN MANAGEMENT
Scan lifecycle commands allow controlling running scans without terminating the maldet process. Scans transition through a state machine:

    running → paused   (--pause)   → running (--unpause)
    running → killed   (--kill)
    running → stopped  (--stop)    → running (--continue)
    running → completed (normal exit)

ClamAV limitation: Daemon ClamAV (clamdscan) scans can only be killed (--kill), not paused or stopped. Standalone clamscan and yara/yr processes support full lifecycle control via SIGSTOP/SIGCONT.

-L, --list-active

    List active (running, paused, stale) scans with status information. Combine with --format for JSON or TSV output. Use --verbose for additional detail (workers, sig version, progress).
    e.g: maldet -L
    e.g: maldet --format json -L

--report active

    View active scans. Equivalent to -L but supports --format json|tsv output and integration with the report system.

--kill SCANID

    Abort a running or paused scan. Writes an abort sentinel for cooperative shutdown, then sends SIGTERM to the scan process (with SIGKILL fallback after 30 seconds). Cleans scan-scoped temporary files and updates meta to state=killed. Also handles stale scans where the process has already exited.
    e.g: maldet --kill 260327-1509.25279

--pause SCANID [DURATION]

    Pause a running scan. Workers enter a sleep loop and external processes (clamscan, yara) receive SIGSTOP. Optional DURATION auto-resumes after the specified time: Ns (seconds), Nm (minutes), Nh (hours), or a bare number (seconds). Without DURATION, the pause is indefinite until --unpause. Not supported for daemon ClamAV (clamdscan) scans.
    e.g: maldet --pause 260327-1509.25279
    e.g: maldet --pause 260327-1509.25279 30m
    e.g: maldet --pause 260327-1509.25279 2h

--unpause SCANID

    Resume a paused scan. Removes the pause sentinel and sends SIGCONT to any stopped external processes.
    e.g: maldet --unpause 260327-1509.25279

--stop SCANID

    Checkpoint and stop a running or paused scan at the current stage boundary. Writes a checkpoint file (format #LMD_CHECKPOINT:v1) preserving the scan state (stage, hits, options, signature version) for later resumption via --continue. Not supported for daemon ClamAV (clamdscan) scans.
    e.g: maldet --stop 260327-1509.25279

--continue SCANID

    Resume a previously stopped scan from its checkpoint. Compares the current signature version with the checkpoint and warns if signatures have changed since the scan was stopped. Restores -co options from the checkpoint. Use --unpause for paused scans instead.
    e.g: maldet --continue 260327-1509.25279

--maintenance

    Run maintenance tasks: rotate oversized history files (hits.hist, quarantine.hist, monitor.scanned.hist, inotify_log), clean up stale scan meta files older than scan_meta_cleanup_age hours, compress completed session files older than maint_compress_age days, and archive sessions older than maint_archive_age days into monthly bundles (session.archive.YYMM.tsv.gz). Intended for cron or manual housekeeping.
    e.g: maldet --maintenance

  Checkpoint Format
The checkpoint file uses a simple key=value format with a #LMD_CHECKPOINT:v1 header line. Fields include: scanid, stopped (epoch), stage (last completed stage), sig_version, workers, total_files, hits_so_far, and options (original -co values). The session hits file is preserved across stop/continue so that accumulated detections are retained.

EXAMPLES

Scan all files under user web roots:
    maldet -a /home/?/public_html

Scan recent files with auto-quarantine and YARA:
    maldet -co quarantine_hits=1,scan_yara=1 -r /home/?/public_html 2

View the most recent scan report:
    maldet -e

List all scan reports:
    maldet -e list

Output a scan report as JSON:
    maldet --json-report SCANID

List all reports as JSON:
    maldet --json-report list

Quarantine all hits from a scan:
    maldet -q SCANID

Start real-time inotify monitoring (foreground):
    maldet -m users

Start real-time inotify monitoring (background daemon):
    maldet -b -m users

Scan a file via the generic hook API:
    hookscan.sh generic /path/to/file

Batch scan files from a list:
    hookscan.sh generic --list /tmp/filelist.txt

View hook scan activity (last 24 hours):
    maldet --report hooks

View hook scan activity filtered by mode:
    maldet --report hooks --mode modsec

Fire an on-demand digest alert:
    maldet --digest

Test email alerting with a synthetic scan report:
    maldet --test-alert scan email

Test Slack digest delivery:
    maldet --test-alert digest slack

SEE ALSO
clamdscan(1), clamscan(1), yara(1), inotifywait(1)

BUGS
Report bugs at https://github.com/rfxn/linux-malware-detect/issues.

AUTHORS
(C) 2002-2026, R-fx Networks <proj@rfxn.com>
(C) 2026, Ryan MacDonald <ryan@rfxn.com>