Skip to content

LMD v2.0.1#478

Merged
rfxn merged 143 commits into
masterfrom
2.0.1
Apr 26, 2026
Merged

LMD v2.0.1#478
rfxn merged 143 commits into
masterfrom
2.0.1

Conversation

@rfxn
Copy link
Copy Markdown
Owner

@rfxn rfxn commented Apr 6, 2026

v2.0.1 Release

82 commits, 219 files changed (+43,881 / -4,422). Full rewrite of scan engine, monitoring, alerting, and packaging.

New Features

  • Native batch scan engine -- 43x faster HEX+CSIG with parallel grep workers; Perl dependency removed
  • SHA-256 hash scanning -- hardware acceleration auto-detection (SHA-NI on x86, SHA2 on ARM); scan_hashtype config (auto/sha256/md5/both)
  • Compound signatures (csig) -- multi-pattern boolean logic (AND/OR/threshold), case-insensitive, wide UTF-16LE, bounded gap wildcards
  • Native YARA scanning -- independent scan stage with yara/yr (YARA-X); custom rules, drop-in directory, compiled rules, batch scanning
  • Monitor mode redesign -- supervisor model replaces double-fork; 12 pre-existing defects resolved
  • TSV session format -- 11-field structured hit records replace plaintext; backward compat via session_legacy_compat
  • Report output -- format text/json/html and --mailto
  • Email alerts reimagined -- HTML+text dual-format template engine; SMTP relay; Slack Block Kit, Telegram MarkdownV2, Discord embeds
  • Hook scanning API -- modsec/ftp/proftpd/exim/generic dispatch; batch input; rate limit; sig masking; escalation and digest
  • Scan lifecycle management -- --kill, --pause/--unpause, --stop/--continue; -L/--list-active (text/json/tsv); --maintenance; checkpoint resume
  • RPM and DEB packaging -- FHS-compliant layout with backward-compatible symlink farm; pkg_fhs_verify_farm auto-repairs at startup
  • Shared library integrations -- alert_lib 1.0.6, elog_lib 1.0.5, tlog_lib 2.0.5, pkg_lib 1.0.8
  • Post-scan hook -- configurable script execution with args/file/json tiers, sync/async, timeout, min_hits threshold
  • ClamAV sig validation gate -- clamscan -d validates before deployment; SIGUSR2 reload gated on validation
  • Portable source-tree execution -- run maldet from git clone or tarball without install.sh
  • Redesigned -e list -- columnar output with header, --all flag, QUAR column, pager, JSON active array

Changes

  • Vendored lib sync: tlog_lib 2.0.5, alert_lib 1.0.6, elog_lib 1.0.5, pkg_lib 1.0.8
  • Alert templates consolidated; HTML rendered on-demand from session data
  • CLI modifier flags (-x, -i, -hscan, -qd, -co, --format, --mailto) are now position-independent
  • scan_workers default changed from "0" to "auto"
  • scan_hexdepth default reduced from 512 KB to 256 KB (covers 98.9% of HEX patterns)
  • FHS log path: /var/log/maldet/ is authoritative; $inspath/logs is a compat symlink
  • --maintenance: compression age raised to 30 days, archive to 90 days
  • Documentation: man page, README, and usage rewritten with full option/config coverage
  • Tests: batsman v1.4.2, per-test 180s timeout, GHA 24-way parallel matrix, JUnit XML

Bug Fixes

  • Background scans (-b): PID tracking used $$ instead of $BASHPID; --kill/--pause/--stop targeted wrong process
  • Security: _safe_source_conf() allowlist (79 vars); conf.maldet 0640; RCE prevention; semver traversal guard; hookscan filename validation; JSON/email header injection prevention
  • ClamAV: sig deployment permissions 644, linksigs ordering, version-gated .hsb, _count_signatures() reads on-disk files, clamselector() logs warning on fallback
  • Monitor mode: 12 defects (graceful shutdown, crash recovery, session rotation, PID guard, ClamAV cache)
  • Alerting: Slack migrated from deprecated files.upload API; Telegram /bot prefix; token security via curl -K
  • Quarantine: correct scan ID in reports, TOCTOU fix, inode naming replaces $RANDOM, batch sig name
  • FreeBSD: uname -s detection, POSIX id -u, portable cp/mv/rm, scan_strlen guard
  • CLI robustness: 19 bare exit statements given explicit codes; missing/empty arg rejection
  • find/ignore: converted to bash arrays; non-existent user/group handling
  • Portability: mktemp replaces $RANDOM/$$; command -v replaces which; grep -E replaces egrep
  • 40+ additional fixes across restore, purge, cron, tlog cursors, temp file leaks, and packaging

Release Checklist

  • CI green -- all OS targets passing (CentOS 6/7, Rocky 8/9/10, Ubuntu 20.04/24.04, Debian 12, YARA-X)
  • UAT -- 99/99 tests pass across all 7 workflows
  • Sentinel review -- SHIP verdict; all release artifacts present; RPM/DEB synced
  • CentOS 6 timeout compat -- --kill-after removed from all call sites
  • Local test suite -- 971 tests, 0 failures (Debian 12 + Rocky 9)
  • Full distro matrix validation before merge
  • Tag v2.0.1 after merge
  • Release tarball via git archive

rfxn added 30 commits March 30, 2026 19:17
…yntax, update copyrights...

- [Fix] critical bug fixes: md5_hash typo, compat.conf logic, maldet.sh syntax
- [Fix] security hardening: mktemp temp files, quote expansions, command -v
- [Change] modernize syntax, update copyrights, version bump to v2.0.1
- [New] BATS test infrastructure with Docker CI; 8 test files, ~77 tests
- [New] expand test suite to 9 OS targets and 17 test files
- [Fix] HEX stdin mode swapped arguments in scan_stage2; test suite hardened for Debian 12
- [Change] drop Ubuntu 22.04 from CI matrix; redundant with 20.04 + 24.04
- [Fix] test suite passes on all 7 OS targets; fix Rocky/CentOS container issues
- [Change] align test infrastructure with APF patterns: arg pass-through, grouped targets
- [Fix] ignore_file_ext, --exclude-regex, --include-regex, and ignore_paths prune broken after eval removal; convert from string variables with embedded quotes to bash arrays for correct find argument passing; issue #446, #438, #450
- [Fix] exit code 1 on invalid CLI args (#366); skip empty monitor scan lists (#459); validate scan_ignore_user/group existence (#335); replace ed with tail/cat for log trimming (#308); fix empty -regex find errors (#72)
- [Fix] quarantine.hist missing sig name (#399); Slack files.upload deprecated (#458); Slack/Telegram alerts in monitor mode (#387)
- [Fix] monitor_check() clamd fatal errors (no reply from clamd) no longer treated as malware hits causing false quarantines; issue #454, #447 [Fix] upgrade install no longer kills inotify monitoring; use systemctl restart when systemd is available; issue #414 [Fix] cron.daily overlapping runs prevented with flock-based lockfile; issue #373 [New] cron.daily Bitrix panel detection (/opt/webdir + /etc/nginx/bx); issue #381
- [Fix] regression audit: cleanup_scanlist wrong path, sigup swapped vars, herpaderpadoo placeholder, undefined $retry_sleep, break outside loop, ignore_sigs elif, inotify cpulimit wrong var, importconf stale Slack scope, $intfunc typo, GPL v2 qualifier, orginal typo, missing ;; before esac [Change] add -E/--dump-report and -p/--purge to CLI usage help [New] tests for maldet.sh/hookscan.sh/compat.conf syntax, quarantine.hist sig name, scan_ignore_group
- [Change] rewrite usage_short() and usage_long() with categorized sections, complete option coverage, argument placeholders, and consistent formatting
… Telegram Bot API URL mi...

- [New] test coverage for clean operations, ClamAV integration, cron daily, and alerting
- [Fix] Telegram Bot API URL missing required /bot prefix before token; issue #461
- [Fix] clamselector() no longer overwrites user scan_max_filesize config; issue #410
- [Fix] tlog line truncation: switch byte-based to line-based tracking; issue #227
- [Fix] panel alerts include signature name; rewrite hit parsing with BASH_REMATCH; InterWorx empty master_domain guard; issue #426
- [Fix] view_report() add "newest" alias, fix email-latest-report bug, replace $EDITOR with cat; issue #336
- [New] cron.daily explicit cPanel detection with /etc/userdatadomains parsing for addon/subdomain docroots; issue #268
- [Change] cron.daily prune uses find -delete instead of xargs rm -f; issue #430
- [Fix] clamselector() warns on clamd test failure before falling back to clamscan; issue #452
- [New] native YARA scanning: scan_yara=1 enables YARA as an independent scan stage using the yara binary (or yr from YARA-X); supports custom rules via custom.yara and custom.yara.d/ drop-in directory; scan_yara_scope controls rule overlap with ClamAV YARA; compiled rules via yarac supported; issue #392, #277, #239
- [New] README.md with comprehensive markdown documentation; update usage_long() with YARA scanning section; update maldet.1 man page with YARA features, --web-proxy option, and 2026 copyright
- [Fix] YARA audit fixes: --disable-warnings for YARA-X, sig count display,       install.sh clamav_linksigs rfxn.yara, man page corrections, variable       quoting, local declarations, Dockerfile precedence; add PLAN.md with       deferred medium-priority items
- [Change] scan_stage_yara() uses --scan-list for batch file scanning,       reducing process invocations from O(N*M) to M+1; YARA stderr       captured and logged via eout instead of discarded
- [Fix] YARA audit fixes: --scan-list fallback for YARA < 4.0, stderr noise       filtering, cpulimit exit code capture via sh -c wrapper,       Dockerfile.yara-x with YARA-X v1.13.0 CI coverage; YARA tests accept       either yara or yr
- [Fix] YARA audit fixes: clean() YARA rescan, per-file fallback exit codes,       YARA-X stderr filter, trap temp file cleanup, hookscan.sh scan_yara       passthrough; add deferred items #11-#17 to PLAN.md
- [New] cron.watchdog weekly watchdog script for independent fallback signature       updates when primary cron.daily is broken or stale; install.sh installs       to /etc/cron.weekly/maldet-watchdog [New] test coverage for update mechanisms: get_remote_file, sigup, lmdup,       cron update integration, and watchdog (22 tests)
- [Fix] scan_stage_yara() deduplicates hits against scan_session, preventing       double-counted files when ClamAV and native YARA both detect the same       file; usage_short() mentions YARA via -co scan_yara=1; signature count       shows YARA(cav) qualifier when native YARA disabled
- [Change] PLAN.md reorganized into 5 phases: correctness bugs, documentation,       YARA hardening, performance/refactoring, CI/infrastructure; added new       audit findings from third review cycle
- [Fix] scan_stage_yara() dedup anchored with end-of-field regex to prevent       substring false-positives; per-file fallback uses sh -c wrapper to       capture YARA exit code through cpulimit; clean() YARA rescan skips       dedup during clean verification and honors clean_check parameter
- [Change] copyright headers updated to 2026 across 10 source files [Fix] CHANGELOG CI matrix corrected to 8-target (was 9-OS); added YARA-X,       removed Rocky 10 and Ubuntu 22.04 not in CI; merged duplicate v2.0.1       date blocks [Fix] README.md cron_prune_days default corrected from 14 to 21 [Change] sigup() signature count uses YARA(cav) qualifier when scan_yara       disabled, matching scan() display [Change] usage_short() YARA hint reformatted as sub-note under -co option [Change] legacy plain-text README replaced with pointer to README.md
- [Fix] import_user_sigs() validates downloaded YARA rules with yr check or       yara before installing to custom.yara; malformed rules are rejected       with warning instead of silently breaking YARA scanning [Fix] scan_stage_yara() validates compiled.yarc with test scan before use;       cross-engine (yarac vs yr) or corrupt compiled rules are skipped with       warning instead of causing scan errors [Change] README.md documents ignore_sigs regex/substring matching behavior,       hit prefix table ({MD5}/{HEX}/{SA}/{YARA}/{CAV}), YARA batch scanning,       hookscan YARA config, and compiled.yarc path
- [New] test coverage for YARA download validation and compiled.yarc       validation (6 tests); exercises import_user_sigs() syntax checking       and scan_stage_yara() compiled rules engine validation
- [Change] scan_stage_yara() refactored: extract _yara_scan_rules() helper       eliminating ~80 lines of duplicated text/compiled rules scan+parse       code; cache YARA binary selection and --scan-list detection in       globals via _yara_init_cache() to avoid repeated fork+exec every       monitor cycle; filter quarantined/unreadable files from YARA file       list via _yara_filter_filelist() at scan() call sites; remove       unused yarac binary discovery from internals.conf
- [Fix] README.md CI badge points to 2.0.1 branch instead of master;       fixes "no status" display since all CI runs are on 2.0.1
- [Fix] README.md CI badge reverted to master branch; badge will be       correct after 2.0.1 merges to master
- [Fix] cron.daily flock lock leaked to backgrounded scans; switched to       CLOEXEC command form (flock -n FILE "$0") so children never       inherit the lock fd [Fix] cron.watchdog version update now runs regardless of sigup result [Fix] README.md md5v2.dat format corrected to HASH:SIZE:{MD5}sig.name.N [New] SHA-256 checksum verification for YARA-X binary in Dockerfile.yara-x [New] test coverage for clean() YARA rescan and YARA(cav) display (3 tests) [New] watchdog sigup-failure resilience test; cron CLOEXEC lock test [Change] Rocky Linux 10 added to CI matrix (9-target); Dockerfile.rocky10       fixed for rockylinux/rockylinux:10 base image and package conflicts
- [Fix] cron.daily update failure logging, README.md config table,       conf.maldet comment typo; add curl to Rocky 8/9 Dockerfiles
… email_ignore_clean comm...

- [Fix] conf.maldet comment typos, email_subj relocation, README.md       config table completeness, usage_short/long fixes, CHANGELOG dedup
- [Fix] email_ignore_clean comment/description corrected in conf.maldet       and README.md; was misleading about suppression behavior [Fix] cron.daily prune comment corrected: said "7 days" but actual       default is $cron_prune_days (21) [Fix] conf.maldet scan_yara_timeout: conflicting "0 = disabled"       annotation clarified to "0 = no timeout"
- [Fix] conf.maldet: slack_subj, telegram_file_caption, quarantine_clean       comments corrected; "retrys" typo fixed [Fix] hookscan.sh: backtick replaced with $(), source arguments quoted [Fix] README.md: -m adds
- [Change] usage_long() documents legacy aliases: -kill, --update,       --update-version, --wget-proxy, --curl-proxy
- [New] run-tests.sh: within-OS parallel test execution via --parallel [N]       flag; round-robin distributes .bats files across nproc*2 Docker       containers with TAP aggregation, group headers, and elapsed time [Change] Makefile: all targets default to --parallel; add test-serial       for old sequential behavior; fix make help regex for digit targets [Fix] 20-cron.bats: add run_cron_daily() helper to bypass cron.daily       random sleep (1-999s) in autoupdate tests — reduces Debian 12       parallel runtime from ~1100s to ~45s [Change] remove orphaned Dockerfile.ubuntu2204 (not in CI); resolves       PLAN.md 6.4
- [Fix] hex FIFO mode 666 → 600; only root's scanning process uses it
- [Fix] conf.maldet world-readable → 0640; protects tokens from local users
- [Fix] hookscan.sh: validate filenames from ModSecurity inspectFile
- [Fix] -co/--config-option: validate values before sourcing
- [Fix] import_conf() safe config sourcing; prevents remote code execution
- [Fix] importconf: add missing variables, fix hardcoded values overwriting user settings
- [Fix] install.sh: remove version gate, preserve logs, fix cron stderr
- [Fix] clean() operator precedence and clean_check rescan
- [Fix] monitor_check() double ClamAV scan and missing YARA filter
- [Fix] monitor_kill() guards empty PID to prevent kill on process group 0
- [Fix] quarantine naming and suspend ordering
- [Fix] checkout() dead code, return, threshold; update changelogs for Phase 10
…ortable JSON par...; 19 bar...

- [Change] CHANGELOG dedup: consolidate 6 near-duplicate entries
- [Fix] Slack multi-channel alerting, portable JSON parsing, Telegram curl
- [Fix] 19 bare exit statements given explicit exit codes
- [Fix] Monitor reload sources compat.conf and sysconfig overrides
- [Change] CHANGELOG updates for Phase 11 alerting, exit codes, monitor reload
- [Fix] test: batch clean accepts exit 1 for already-quarantined files
- [Fix] restore() validates .info file paths before restoring
- [Fix] systemd PIDFile + EnvironmentFile corrections
- [Fix] Uninstall watchdog cleanup + install backup safety
- [Fix] FreeBSD detection: replace broken OSTYPE checks with uname -s
- [Change] CHANGELOG updates for Phase 12 security, service, install, FreeBSD fixes
- [Fix] test: correct install.sh path and os_freebsd grep pattern in tests
- [Fix] Slack/Telegram alerting: curl exit code checks and API error extraction
- [Fix] run-tests.sh parallel mode: preserve BATS_ARGS as array
- [Fix] CI pull_request trigger: add version branch pattern
- [Change] CHANGELOG updates for Phase 13 alerting, test, CI fixes
- [Change] CHANGELOG dedup: merge 7 related entry groups, shorten 2 verbose entries
- [Fix] Security hardening: functions file low-severity fixes (F-039, F-054, F-059, F-087)
- [Fix] Security hardening: CLI and install low-severity fixes (F-045, F-074, F-087)
- [Change] CHANGELOG updates for Phase 14 security hardening fixes
- [New] tests/26-special-paths.bats: special characters and path handling tests
- [Change] CHANGELOG updates for Phase 14b special-char path tests
- [Change] Rewrite maldet.1 man page following man-pages(7) conventions
- [Fix] Man page install and uninstall gaps
- [New] Man page smoke tests in 01-install-cli.bats
- [Change] CHANGELOG updates for Phase 15 man page rewrite
- [Change] Progress display: eliminate 4 forks per file in scan loop
- [Change] Replace md5sum
- [Change] Replace echo
- [Change] Replace echo
- [Change] Consolidate get_filestat() stat calls from 4-5 forks to 1
- [Change] Replace deprecated $[expr] with $((expr)) arithmetic syntax
- [Change] ClamAV hit parsing: replace echo
- [Change] Monitor mode: replace cat
- [Change] Replace basename with parameter expansion in hit/quarantine paths
- [Change] CHANGELOG updates for Phase 16 native scanning performance optimization
- [Fix] HEX hit name trailing space from perl output
- [Fix] CHANGELOG: add missing entry for HEX hit name trailing space fix
…functions; CHANGELOG upd...

- [Fix] cron.daily: remove LMDCRON dead code, fix flock error swallowing
- [Change] Convert $$-tempfile paths to mktemp in functions
- [Change] CHANGELOG updates for Phase 17a cron cleanup and tempfile hardening
- [Fix] sigup() guard clean rules cp with directory existence check
…..; Dockerfile centos6/7; R...

- [Fix] Portability and functional fixes for chown, purge, sed, and scan elapsed time
- [Fix] Dockerfile centos6/7: isolate optional yara install in subshell
- [Change] Replace egrep with grep -E and fix usage_long() help text
- [Change] Convert for-in-cat to while-read in core scan/quarantine paths
- [Change] Convert for-in-cat to while-read in config, monitor, and misc paths
- [Change] CHANGELOG updates for Phase 19 shell modernization
- [Fix] CLI: reject empty SCANID for -q/-n/-s; fix -f background scan log message
- [Change] Standardize bare wc to $wc; replace for-in-$(ls) with glob in view_report()
- [Change] CHANGELOG updates for Phase 20 CLI and standards fixes
- [Fix] cpulimit comparison: move stderr redirect after ] for clarity
- [Fix] genalert() digest: fix email_subj mutation, modernize shell patterns
- [Fix] ELK integration: enable column formatting, fix bare curl and for-in-cat
- [Change] CHANGELOG updates for Phase 21 alert pipeline and ELK fixes
- [Change] backtick → $() modernization: scan pipeline functions
- [Change] backtick → $() modernization: quarantine, restore, clean, ClamAV
- [Change] backtick → $() modernization: monitor, checkout, sigignore, updates
- [Change] backtick → $() modernization: maldet CLI, uninstall.sh, CHANGELOG
- [Change] tlog: unified library, multi-mode tracking, fix trim cursor bug
- [Fix] tlog BASERUN: install-time path replacement for cursor storage security
- [Change] test infrastructure: migrate to batsman shared BATS framework
- [Change] CI: pin batsman workflow ref to v1.0.1 tag
- [Change] extract tlog_lib 2.0.1 canonical files
- [Fix] CI: Rocky 10 base image name in batsman submodule
- 2.0.1
- [Fix] clamselector(): remove invalid --fdpass from remote clamd TCP path
- [Fix] genalert(): add timeouts to Slack and Telegram curl calls
- [Fix] get_remote_file(): remove hardcoded curl/wget fallback paths
- [Fix] hookscan.sh: replace bare pidof with $pidof variable
- [Fix] install/service infrastructure: sysconfig cleanup, compat.conf, chkconfig guards
- [Change] CHANGELOG updates for Phase 23
…fix; store regex pattern...

- [Fix] gen_panel_alerts() temp file cleanup; F-057
- [Fix] storename_prefix: replace $RANDOM with /dev/urandom; F-047
- [Change] store regex patterns in variables for [[ =~ ]]; F-070
- [Change] add local declarations to eout, restore, restore_hitlist, clean_hitlist; F-071
- [Change] document --alert-daily / --monitor-report; F-098
- [Fix] README scan_cpunice range, usage_short --beta flag; F-075, F-076
- [Change] CHANGELOG updates for Phase 24
- [Fix] tests/24-security.bats: adjust head offset for hookscan regex variable
- [Fix] lmdup() capture install.sh exit code and log on failure; F-025
- [Fix] lbreakifs() remove spurious backspace from IFS; F-050
- [Fix] replace hardcoded usermod and chkconfig paths with discovered binaries; F-065
- [Change] add version/copyright headers to 6 files missing them; F-088
- [Change] CHANGELOG updates for Phase 25
- [Fix] scan_user_access_minuid inconsistencies + remove orphaned variables; F-079, F-080
- [Change] document scan_find_timeout min 60 + fix hookscan ClamAV docs; F-077, F-078
- [Change] CHANGELOG updates for Phase 26
- 2.0.1
- [Fix] regression fixes R-1 through R-5 from pre-merge review
- [Change] documentation fixes R-6, R-7, R-8 from pre-merge review
… install/uninstall syste...

- [Fix] test robustness fixes R-9 through R-14 from pre-merge review
- [Fix] install/uninstall systemd+SysV overlap; drop F-085, F-083, F-087 as FP
- [Change] test suite deduplication and cleanup; 330 → 320 tests
… monitor filter accumula...

- [Fix] grep without -F on literal file paths in clean/restore; F-003
- [Fix] monitor filter accumulation; extract _build_scan_filters(); F-001
- [Fix] colon-containing path truncation in quarantine metadata; F-002
- [Change] changelog updates for F-001, F-002, F-003 audit remediations
- [Fix] colon-containing path truncation in session.hits parsing; F-002
- [Change] changelog: add session.hits colon fix detail to F-002 entry
- [Change] centralize scan temp-file cleanup into _scan_cleanup(); F-007, C-004
- [Change] extract _scan_build_filelist() from scan(); F-006, C-004
- [Change] extract _scan_run_clamav() from scan(); F-006, C-004
- [Change] extract _scan_run_native() from scan(); F-006, C-004
- [Fix] non-destructive signature ignore filtering; F-004
- [Fix] prevent session file mutation on sendmail path; F-015
- [Fix] quote unquoted variables in genalert() and clean(); F-012
- [Fix] replace hardcoded install paths in importconf and hexfifo.pl; F-017, F-018
- [Fix] document scan_days config variable; F-019
- [Fix] stop monitor before uninstall; add JSON escaping for ELK/Slack; F-025, F-027
- [Fix] restore_hitlist dead code; scan days lower-bound validation; F-066, F-070
- [Fix] ClamAV lstat ERE escaping; remove dead get_return variable; F-068, F-069
- [Change] replace 17 wc -l FILE
- [Change] replace subshell greps with builtins; stderr for fatal errors; F-036, F-042
- 2.0.1
- [Change] tlog_lib.sh: update to canonical 2.0.2
- [Fix] 4 bugs found during regression verification
- [Change] remove dead code: sigignore() stub and redundant postrun _scan_cleanup
- [Change] extract _run_yara_scan, _send_email, _clamscan_fallback helpers
- [Change] quar_get_filestat() single-pass parser
- [Change] extract _build_nice_command, _count_signatures, _clamd_retry_scan, _process_clamav_hits
- [Change] extract _quarantine_file, _clean_rescan, _inotify_trim_log helpers
- [Fix] Perl trailing space in HEX output; antipattern cleanup
…g file); hex extraction/loo...

- [Change] install.sh: extract _install_core() helper; consolidate clamav_paths
- [Change] remove PLAN.md from tracking (working file)
- [New] hex extraction/lookup helpers; [Change] scan_stage2 from Perl to grep
- [New] batch+parallel hex scanning in _scan_run_native; scan_hex_workers config
- [Change] remove hexfifo.pl, hexstring.pl, FIFO infrastructure; Perl no longer required
- [Change] update hex scanner tests for native engine
- [Change] document native hex scanner, scan_hex_workers, wildcard support
- [Fix] _hex_compile_pattern sed commands require -E for ERE capture groups
- [Fix] _scan_run_native: skip MD5-hit files in HEX/strlen passes; fix strlen path handling
- [Change] consolidate scan_hexfifo/scan_hexfifo_depth into scan_hexdepth
- [Fix] tighten directory permissions for sigdir, logdir, tmpdir; F-028
- [Fix] hide Telegram bot token from /proc/PID/cmdline; F-029
- [Fix] add argument validation for 4 CLI options; F-043
- [Fix] preserve conf.maldet.hookscan and pub/ on upgrade; F-060
- [Change] unified scan progress output across native and ClamAV engines
- [Fix] four bug fixes in functions file; F-008, F-009, F-011, F-016
- [Fix] CLI validation for --alert-daily and -e/--report; F-044, F-045
- [Fix] install/uninstall hardening and binary guards; F-024, F-026
- [Fix] document --monitor RELOAD and --mkpubpaths in CLI help; F-046, F-050
- [Change] add copyright headers to clean rules and templates; F-086
- [Change] rename scan_hex_workers to scan_workers; extract _resolve_worker_count()
- [New] batch parallel MD5 scanning via _md5_batch_worker()
- [New] tests for parallel MD5 scanning and scan_workers compat mapping
- [Change] reorder scan options in conf.maldet and importconf for consistency
- [Fix] add /etc/cron.daily to test Dockerfiles so cron tests run on minimal images
- [Fix] gensigs() temp file leak on repeated calls
- [Fix] _scan_cleanup() gap and _md5_batch_worker FreeBSD temp
- [Change] remove dead md5_hash pre-set and guards
- [Fix] _hex_compile_pattern case scope; add 2>/dev/null comments
- [Change] remove dead variables, compat mappings, and redundant conditional
- [Change] rename $ver to $lmd_version across codebase
- [Change] add 43x scan engine benchmark to README
- [Change] deduplicate and restructure v2.0.1 changelogs
…liminate useless cat-pipe p...

- [Fix] security hardening: sig permissions, email subject injection, hookscan tmpdir, SysV PID check
- [Change] shell idiom modernization: ! -z to -n, ! == to !=
- [Change] eliminate useless cat-pipe patterns in functions
- [Change] deduplicate prerun() with loops and helpers
- [Fix] add grep -h to multi-file signature merges in gensigs()
- [Fix] import_conf compat.conf re-source; $curl guards for ELK and Telegram
- [Change] sigup() dedup: use _count_signatures() for corruption check
- [New] exit code assertion tests (F-053)
- [New] syntax check tests for tlog_lib.sh, cron.watchdog, uninstall.sh (F-054)
- [New] scan_user_access_minuid test coverage (F-055)
- [New] -co injection negative tests for semicolon, pipe, ampersand (F-056)
- [Change] normalize mixed tabs/spaces to tabs in functions (F-093)
- [New] lmdup --force test and cron assertion hardening (F-051, F-052)
- [New] .dockerignore excludes dev/working files from build context (F-057)
- [Change] YARA-X CI artifact upload and Dockerfile arch param (F-058, F-059)
… Document Discord alerti...

- [New] Integrate shared alert_lib, replace all inline alerting code
- [Change] Document Discord alerting and alert_lib.sh in man page and README
- [Fix] Template token syntax, duplicate panel messaging, install.sh hardening
- [Change] Update shared alert_lib to v1.0.2
- [New] Integrate shared elog_lib for structured event logging
- [New] Add elog_event() calls and elog integration tests
… QA fixes; QA fixes

- [New] Integrate shared pkg_lib for packaging, install, and uninstall
- [Fix] QA fixes: shellcheck directive, changelog accuracy
- [Fix] QA fixes: legacy backup pruning, uninstall.sh service removal consistency
- [Fix] QA integration fixes; update vendored elog_lib, alert_lib, pkg_lib
- [Change] Test suite optimization: light reset, remove redundant tests, shared quarantine setup
- [Fix] Suppress repeated config_loaded audit events
- [Change] update vendored tlog_lib.sh 2.0.2 to 2.0.3; adds inline error suppression comments and corrupt-archive stderr handling
…..; update vendored pkg_lib...

- [Fix] Documentation sync, man page expansion, changelog formatting
- [Change] update vendored pkg_lib.sh 1.0.1 to 1.0.2
- [Fix] move config_loaded elog_event to monitor mode only
- [New] UAT test suite: 8 scenario files, 43 tests for sysadmin-perspective testing
- [Fix] elog_event audit log test needs scannable content
- [Change] changelog: update UAT entry with exact test count and uat_lmd_last_scanid
- [New] CI: add UAT acceptance test job
- [Change] Condense CHANGELOG.RELEASE: restructure sections, merge overlapping entries
- [Change] UAT fragility fixes: replace hardcoded sleep with polling waits
- [New] UAT scenario expansion: 6 new test files adding 30 sysadmin-perspective tests
- [Fix] UAT QA fixes: hardcoded path, dead code, unchecked flock
- [Change] Update batsman submodule to v1.2.0
- [Change] Changelog dedup: consolidate internal entries, merge UAT scenarios
- [Fix] CI: UAT job Docker build context error
…..; Batch hit processing pi...

- [Change] Log resolved worker count in native scan engine startup
- [New] Batch hit processing pipeline functions
- [Change] Native engine: batch hit processing for MD5 and HEX passes
- [Change] ClamAV engine: batch hit processing via _flush_hit_batch()
- [Change] YARA engine: batch hit processing via _flush_hit_batch()
- [Change] String length scan: batch hit processing via _flush_hit_batch()
- [Change] CLI quarantine: batch hit processing for quar_hitlist()
- [Change] Migrate scan_stage1/2 to batch pipeline; remove record_hit()
- [Change] Remove quarantine() — replaced by _batch_quarantine() pipeline
- [Fix] QA fixes for batch hit processing pipeline
- [Change] Changelog: add batch hit processing pipeline entry
- [Fix] Batch pipeline: stat format and strlen eout compatibility
- [Fix] Quote $md5sum in _md5_batch_worker xargs call
- [Change] Batch quarantine: extract dest paths once; pre-load inode map
- [New] Live worker progress indicator for native scan engine
- [Change] Scan progress: per-file throughput, elapsed timers, worker formula
…n, panel, and diges...; HTM...

- [New] Email alert system: lmd_alert.sh, config vars, source chain
- [New] Text email templates for scan, panel, and digest alerts
- [New] HTML email templates for scan, panel, and digest alerts
- [Change] Refactor gen_report() and genalert() to template engine
- [Change] Messaging templates: per-entry formatted Slack/Telegram/Discord
- [Change] Cleanup: remove .etpl files, update docs for email redesign
- [Fix] Messaging dispatch: attach scan report, capture handler errors
- [Fix] Alert rendering: hit type regex, quarpath collapsing, JSON injection, summary tokens
- [Fix] Restore exit code, compat mapping, mktemp exit code masking
- [Change] Scan progress TTY guard, alert render dedup, stale comment cleanup
- [Change] Installer output: header-first layout with section labels
- [Fix] Email delivery error when HTML session file missing
- [New] Tests for HTML session file lifecycle and email format fallback
- [Change] On-demand HTML rendering for email reports of past sessions
- [Change] HTML email template refactor; [Fix] on-demand render metadata
- [Change] Responsive HTML email templates with summary-at-top redesign
- [Change] On-demand HTML email rendering; clean scan celebration
- [Change] O(1) alert entry rendering via single-pass awk
- [Fix] dirty scan HTML email: resolve hits file before genalert renders template
- [Fix] test 71: use reliable BATS assertion for negative HTML content check
- [Fix] sigup() md5 verification: use absolute tmpwd paths for hash checks
- [Fix] _safe_source_conf(): block PATH/LD_PRELOAD override from remote config
- [Fix] clean(): use $file_signame parameter for clean rule lookup, not global $hitname
- [Fix] Slack API token: use curl -K config file to prevent /proc exposure
- [Fix] import_conf(): guard base config re-source when CLI -co overrides applied
- [Fix] _clamd_retry_scan(): add explicit output file parameter with backward-compat default
- [Fix] _process_clamav_hits(): use greedy sed to handle colons in filepaths
- [Change] genalert(): decompose into thin dispatcher and four private helpers
- [Change] genalert decomposition: add clarifying comments and messaging contract test
- [Change] _yara_scan_rules(): convert dynamic-scoped vars to explicit parameters
- [Fix] ELK URL scheme guard; [Change] F-007/F-006 regression tests
- [Change] monitor_check(): migrate native scan to _scan_run_native() batch path
- [Fix] monitor_check() batch path: mktemp guard; document find_results lifecycle and inotify_verbose scope
- [Fix] CI: UAT artifact upload path corrected to /tmp/uat-output/ (B-025)
- [Fix] install.sh: apply MONITOR_MODE migration after sysconfig creation (B-021)
- [Fix] lmdup(): validate + strip version string before path construction (F-002)
- [Fix] LOGIC/INSTALL/PERF/MISC audit remediation — 10 fixes across functions and install.sh
- [Change] Update elog_lib.sh to v1.0.3: FQDN host field
… shell-pattern fixup — c...

- [Fix] SHELL_PATTERN audit remediation — SP-01/02/04/06/SI-01/SI-02
- [Fix] shell-pattern fixup — cd guard, stat portability, awk JSON, changelog
- [Fix] get_panel_contacts(): awk JSON accumulator handles any field ordering
- [Fix] doc sync: man page date, shellcheck for maldet.sh, document -qd/-hscan
- [Fix] test suite: remove 4 redundant tests (dedup pass)
- [Fix] import_conf(): document _lmd_cli_co_applied guard with inline comment
- [Fix] changelog dedup: 129 → 88 entries for v2.0.1
- [Fix] UAT-001: exit 1 on nonexistent scan path; [New] UAT-003: add -v/--version flag
- [Fix] doc sync: add -v/--version to README CLI reference table
- [New] UAT suite expansion: 5 sysadmin workflow test files (15-19)
- [Fix] doc audit: 7 documentation corrections across README and usage text
- [Fix] CI: UAT-001 regression test scans clean tmpdir instead of /tmp
- [Change] CLI modifier flags (-x, -i, -hscan, -qd) are now position-independent
- [Fix] clamav_linksigs() empty lmd.user.ndb/hdb causes ClamAV "Malformed database"
… CI; SHA-256 hash-based ...

- [Fix] _process_clamav_hits() YARA prefix {YARA\} on bash 4.x
- [Fix] CI: CentOS 6 test failures due to /usr/bin/rm path
- [New] SHA-256 hash-based scanning with CPU hardware auto-detection
- [Change] Replace importconf heredoc with pkg_config_merge(); vendor pkg_lib v1.0.3
- [Fix] Restore conf.maldet 640 permissions after config merge on upgrade
- [Change] Sync vendored pkg_lib.sh to v1.0.3 release
- [Change] Update verification prefers SHA-256 over MD5 with graceful fallback
… Rename import_custsigs_...

- [New] Compound signature (csig) scanning with multi-pattern boolean logic
- [Change] Rename import_custsigs_*_url to sig_import_*_url for consistency
- [Fix] Address SE and Challenger review findings for csig integration
- [Fix] Portability: command cp for clamav_linksigs/gensigs; cd guards; shell standards
- [New] Tests: compat.conf sig_import renames, csig import URL, threshold=0 guard
- [Fix] Security, logic, and documentation fixes from PR review
- [Fix] Security, logic, and comment cleanup from mock PR review
- [New] Tests: _safe_source_conf allowlist, -co validation, quar_hitlist dedup
- [Fix] Remove invalid --no-absolute-names tar flag; fix Docker sig determinism
- [Fix] _count_signatures() reads CSIG/SHA-256 counts from on-disk sig files
- [Change] scan_workers default to "auto"; reframe scan_hashtype docs
- [Fix] Remove stale .ca.def symlink; renamed to importconf in 0dadd52
- [Fix] Update vendored pkg_lib.sh to v1.0.4
… alert_lib.sh to v1.0.5; Up...

- [Change] Pin batsman submodule to v1.2.2
- [Change] Update vendored alert_lib.sh to v1.0.5
- [Change] Update vendored pkg_lib.sh to v1.0.5
- [Fix] CI: remove destructive sig restoration; fix SHA-NI UAT failures
- [Fix] CI: UAT tests use unique payloads to avoid CDN sig interference
- [Fix] Add changelog entries for UAT sentinel payload fixes
- [Change] Update vendored tlog_lib.sh and tlog to v2.0.4
- [Change] Update vendored elog_lib.sh to v1.0.4
- [New] TSV session helpers: _session_is_tsv, _session_read_meta, _session_write_header, _session_resolve
- [Change] TSV canonical session format: write-side atomic migration
- [New] Legacy plaintext compatibility for TSV session format
- [Change] Alert pipeline migrated to TSV session format
- [Change] Monitor mode migrated to TSV session format
- [Change] Render pipeline expanded to 11-field TSV with enriched tokens
- [New] JSON report output via --json-report CLI command
- [Change] Dead code review: update stale comment, retain legacy helpers
- [Change] Documentation: --json-report, session_legacy_compat, TSV format
- [New] Test suite for TSV session format and JSON report output
- [Fix] Sentinel remediation: view_report TSV list, text rendering, JSON escaping
- [Fix] UAT remediation: JSON list schema consistency
- [Fix] QA remediation: bare rm in quar_hitlist legacy path
- [Fix] Test suite dedup, perf optimization, and CI failure remediation
…_sigs()

- [New] Report CLI: --format, --mailto modifiers; legacy JSON support
- [Fix] Legacy hitlist parser: sanitize bash 4.x {TYPE\} prefix artifact
- [New] _clamav_validate_sigs(): ClamAV database validation before deployment
- [Change] clamav_linksigs(): stage-validate-deploy gate; issue #467
- [Change] sigup(): flock serialization and SIGUSR2 validation gating
- [New] sigup_interval config variable for independent sig update cron
- [New] cron.d.sigup template for 6-hourly signature updates
- [New] --cron-sigup CLI handler for independent sig updates
- [Change] install.sh: install/remove cron.d.sigup based on sigup_interval
- [New] Test suite for --cron-sigup handler and sigup_interval config
- [Change] Documentation: ClamAV validation gate, sigup_interval, --cron-sigup
- [Fix] QA/UAT remediation: flock self-deadlock, test sleep, upgrade path
- [New] Tests: YARA rule validation in ClamAV deployment path
- [Fix] clamav signature validation: /dev/null false positive, diagnostics, log style
- [New] CSIG batch compiler: dual-format output with subsig dedup
- [New] _hex_csig_batch_worker(): merged HEX+CSIG batch scanner
- [Change] Merge HEX+CSIG into single scan pass via _hex_csig_batch_worker
- [Change] Delete _hex_batch_worker() and _csig_batch_worker()
- [Change] Changelog: merged HEX+CSIG batch engine
- [Change] Scan output: gensigs timing, stage listing, hex+csig progress label
- [Change] gensigs: bulk awk HEX classifier replaces per-pattern bash loop
- [Fix] Sentinel: remove dead _hex_line var, add .hex_wc_tmp to _scan_cleanup
- [Change] Scan stages: reorder strlen after YARA in all paths
- [Change] Consolidate _clean_rescan() around batch worker; delete legacy serial scan
- [Change] Remove old CSIG compiled format; batch format is sole output
- [Change] Changelog: scan path consolidation entries
- [Fix] quarantine_suspend_user(): quote $user to prevent word splitting
- [Change] Remove dead _csig_extract_prefix(); superseded by batch classifier
- [Change] install.sh: extract _read_conf_value() helper; deduplicate sigup_interval reads
- [Change] Unify _md5_batch_worker/_sha256_batch_worker into _hash_batch_worker()
- [Fix] _hash_batch_worker: explicit label param; fixes temp file naming mismatch
- [Change] Changelog: _hash_batch_worker label param fix
…pe_ere()

- [New] Config: digest_interval, digest_escalate_hits, monitor_paths_extra
- [New] _monitor_parse_interval(): digest interval parser with unit tests
- [New] _monitor_escape_ere(): ERE metacharacter escaper for ignore_inotify; fixes defect #3
- [New] _monitor_filter_events(): single-awk event filter replaces 4-process pipeline
- [New] _monitor_append_extra_paths(): additive path composition for monitor mode
- [Change] Monitor mode: foreground supervisor replaces double-fork orphan model
- [Fix] Remove dead cleanup_scanlist() references; fix _clamd_retry_scan signature
- [Change] Service files: Type=simple supervisor, init script uses -b daemonization
- [New] install.sh: monitor_paths.extra lifecycle, upgrade advisory, -b restart
- [Change] Documentation: monitor mode redesign, 3 new config vars, behavioral changes
- [Change] Tests: update monitor UAT for foreground supervisor and -b daemonization
- [Fix] Test: update maldet.service assertion for Type=simple (was PIDFile)
- [Fix] Sentinel remediation: digest_interval disable bug, Rocky 9 test fix
- [Change] Changelog: inotify_verbose deprecation note (sentinel finding)
- [Change] Create lmd.lib.sh sourcing hub; slim maldet source chain
- [Change] Extract lmd_config.sh from functions monolith
- [Change] Extract lmd_init.sh from functions monolith
- [Change] Extract lmd_clamav.sh from functions monolith
- [Change] Extract lmd_sigs.sh from functions monolith
- [Change] Extract lmd_engine.sh from functions monolith
- [Change] Extract lmd_yara.sh from functions monolith
- [Change] Extract lmd_quarantine.sh from functions monolith
- [Change] Extract lmd_session.sh from functions monolith
- [Change] Merge genalert dispatch functions into lmd_alert.sh
- [Change] Extract lmd_scan.sh from functions monolith
- [Change] Extract lmd_monitor.sh from functions monolith
- [Change] Extract lmd_update.sh from functions monolith
- [Change] Move usage functions to maldet; replace functions with compat shim
- [Change] install.sh: chmod 750 for new sub-libraries; man page: update FILES section
- [Change] Changelog: library decomposition (functions → 11 sub-libraries)
- [Change] Delete functions shim; redirect $intfunc to lmd.lib.sh
- [Change] Test suite dedup: prune 5 low-value tests, merge 6 tautological into 2
- [Change] Changelog: deduplicate v2.0.1 entries (179 → 54); merge 12 topic groups,          drop 14 internal/CI-only entries, fix section structure (4 → 3 sections),          resolve vendored library version conflict, sync CHANGELOG.RELEASE
- [Fix] Monitor mode: replace greedy awk sub() with match() in _monitor_filter_events
- [Fix] Monitor mode: replace string variable with array for inotifywait --exclude arguments
- [Fix] Scan engine: isolate worker temp files using parent PID namespace
- [Fix] Scan cleanup: scope lmd_scan.sh temp files to $$ PID namespace; Ref: F-SCAN-001
- [Fix] Scan cleanup: scope lmd_engine.sh temp files to $$ PID namespace; Ref: F-SCAN-001
- [Fix] Scan cleanup: scope lmd_yara.sh temp files to $$ PID namespace; Ref: F-SCAN-001
- [Fix] Scan cleanup: scope lmd_clamav.sh temp files to $$ PID namespace; Ref: F-SCAN-001
- [Fix] scan(): exit 1 when all scan paths do not exist; Ref: F-SCAN-002
- [Fix] _hash_batch_worker: detect md5sum/sha256sum backslash-escaped output; Ref: F-ENG-001
- [Fix] _hex_csig_batch_worker: eliminate O(N*M) grep forks in CSIG rule evaluation; Ref: F-ENG-002
- [Fix] _hex_csig_batch_worker: preload sigmap cache, eliminate per-hit awk forks; Ref: F-ENG-003
- [Fix] scan_strlen: skip on FreeBSD where wc -L is not available; Ref: F-ENG-004
- [Change] Documentation: sync man page and README for adversarial review fixes
- [Fix] Test suite: fix 4 test failures from adversarial review remediation
- [Fix] 03-scan-hex.bats: fix sigmap cache test to grep for actual signame not {HEX} prefix
… _format_number() helper...

- [New] _resolve_clamscan() and _resolve_yara() auto-detection; conf.maldet defaults to auto
- [New] _format_number() helper; _count_signatures() hash dedup and YARA label logic
- [New] clamav_unlinksigs() cleanup; gensigs() gates ClamAV linking on scan_clamscan
- [Change] Wire _resolve_clamscan/_resolve_yara into scan/sigup/monitor; new sig output format
- [Fix] Test assertions: update for new sig output format (MD5/SHA, YARA labels, comma numbers)
- [New] Tests: scan engine auto-detection, output format, clamav_unlinksigs, compat
- [Fix] Test suite: update assertions for scan_clamscan=auto default and output format changes
- [Change] Documentation: scan_clamscan/scan_yara auto mode, sig output format
- [Change] Scan progress: replace worker count with 'elapsed' label
- [Fix] Production defects: sig perms, --report latest, list alignment, tmp cleanup, sigup ver
- [Change] Changelog: production defect fixes (P1-P5)
- [Fix] Sentinel remediation: doc sync, FreeBSD stat, early-exit cleanup
- [Fix] CI: align workflow ref v1.2.2, increase timeout to 25m
- [Fix] Tests: SHA-NI scan_hashtype + ignore_sigs case sensitivity
- [Fix] Docker: add bsdmainutils/bsdextrautils for column command
- [New] Spec: hookscan API design with alert/digest/test-alert
- [Fix] Scan: extract hitname from session TSV for hook output
- [Change] Rewrite hookscan.sh: mode dispatch, validation, timeout
- [Change] Scan: hook hit log + alert suppression + escalation
- [New] Hookscan: file list support (--list and --stdin)
- [Change] Unified digest: 5 sources, hook section, --digest CLI
- [New] Report: --report hooks with time and mode filters
- [New] Test alert framework: --test-alert {scan
- [Change] Docs: hookscan API, test alerts, digest, man page
- [Fix] Tests: hook.hits.log path uses sessdir not var/sess
- [Change] Changelog: hookscan API, digest, test alerts
- [Fix] Sentinel remediation: report field offset, digest isolation, test cleanup
- [New] Documentation convention: level-2 README, banners, companion files
- [Change] Redesign banner SVGs: scan activity visualization replaces name-repeat
- [New] Hookscan: enforce rate limit and signature masking
rfxn added 29 commits April 17, 2026 18:29
[Change] Remove inotify_verbose config var — was declared and documented
         but had no code consumer; per-file ClamAV logging was superseded
         by batch engine log messages in earlier 2.0.1 work. Supersedes
         the prior deprecation entry in CHANGELOG.
[Change] Remove telegram_file_caption config var — declared and
         documented as the Telegram upload caption, but alert_lib's
         _alert_deliver_telegram passes an empty caption unconditionally;
         advertised behavior never shipped.
[Change] Strip both from -co allowlist (lmd_config.sh), man page
         (maldet.1), README.md configuration table, and conf.maldet.
         Add DELETED entries to CHANGELOG.VARIABLES.
…ssue #483

[Fix] _lmd_render_json_list() reports[]: buffer both TSV-index and
      legacy-session passes into a sort buffer and emit in
      started_epoch-descending order (newest first). Pre-fix code emitted
      index entries in append-order followed by legacy entries, producing
      a misordered reports[] array (e.g. Mar 29 legacy after Apr 18 TSV).
      Mirrors the text-path merge-sort in lmd_session.sh.
[New] _lmd_render_json_list() reports[]: additive started_epoch integer
      field alongside the existing started string. Pass 1 uses
      session.index field 2 (already stored); pass 2 derives via
      command date -d with fallback to 0, matching
      _session_index_rebuild at lmd_lifecycle.sh. No schema break --
      existing consumers parsing started remain unaffected.
[Change] CHANGELOG + CHANGELOG.RELEASE: append v2.0.1 Bug Fixes entries for
      both fixes (issue #483 joins the existing section alongside
      #480/#482).
#483

[New] tests/31-json-report.bats: 3 new cases — (1) reports[] sorted by
      started_epoch descending across TSV + legacy passes, (2) started_epoch
      present as integer and matches date -d on started string, (3) legacy
      session entries derive started_epoch via pass-2 date fallback.
[Change] CHANGELOG + CHANGELOG.RELEASE: note test coverage in v2.0.1
      Bug Fixes.
…reports; issue #483

[New] _lmd_render_json_list() active[]: emit started + started_epoch
      from _meta_started_hr / _meta_started (already loaded by
      _lifecycle_read_meta).
[New] _lmd_render_json_list() stopped[]: emit started + started_epoch
      + stopped_epoch for symmetry with reports[]; existing stopped_hr
      preserved (no schema break).
[New] tests/31-json-report.bats: 2 BATS cases using synthetic
      scan.meta.* fixtures to assert active[] and stopped[] carry
      started_epoch; stopped[] asserts stopped_epoch.
[Change] CHANGELOG + CHANGELOG.RELEASE: v2.0.1 Bug Fixes — parity entry.
[Change] _lmd_render_json_list() sort-buffer preamble: drop 3 lines that
         duplicate the ticket + commit-message narrative about the
         pre-fix misorder; keep the load-bearing canonical-reference and
         tab-safety invariant in 4 lines.
[Change] tests/31-json-report.bats test 31: drop the trailing WHAT
         comment above the started_epoch regex — the regex already
         expresses the assertion.
[Fix] Three glob-filter sites (_lmd_render_json_list in lmd_alert.sh;
      _resolve_latest_session_id and _view_session_list pass 2 in
      lmd_session.sh) used session.[0-9]* to enumerate legacy plaintext
      sessions and only excluded .tsv./.hits. variants — picking up
      stale session.N.html artifacts left by pre-on-demand-HTML code
      paths. _parse_session_metadata then slurped multi-MB HTML files
      line-by-line searching for FILE HIT LIST markers that never
      appear in HTML. Local reproducer with 3 HTML artifacts (one 23 MB,
      491k lines): maldet -e list --format json took 12.4s; after fix
      0.5s (24x). Filter now excludes *.html in all three sites.
[New] tests/31-json-report.bats: regression test 33 fabricates a
      session.NNN.html with embedded "SCAN ID:" / "STARTED:" lines
      (pre-fix _parse_session_metadata would parse these and emit as
      a report); asserts absence from output. Guards the class — same
      filter bug repeated across text + json + latest-resolution paths.
[Change] CHANGELOG + CHANGELOG.RELEASE: v2.0.1 Bug Fixes entry.
…ection drift

[Change] Collapse issue #482 into one consolidated [Fix] (field parity + scaling)
[Change] Collapse issue #483 into one consolidated [Fix] (sort + started_epoch + .html glob)
[Change] Collapse issue #480 into one [New] + one [Fix] (two-file union model)
[Change] Correct #483 scope — only reports[] is globally sorted; active[]/stopped[] gain started_epoch for consumer-side sortability
[Change] Tag #482's three Lifecycle-JSON / _json_escape_string [Change] entries with issue ref
[Change] Move 14 mis-tagged entries to correct sections (Fix→Fixes, New→Features, Change→Changes)
[Change] Drop internal-only entries — comment-normalization T1/T2/T3, batsman CI-infra bumps, test-only announcements
[Change] Drop fluff — docs-housekeeping entries for features already announced as [New]
[Change] Trim verbose multi-line entries — vendored-lib sync, DEB override_dh_fixperms, --test-alert digest, _json_escape_string sed→bash
[Change] Collapse Monitor 12-defects [Fix] into the parent [New] supervisor entry (was duplicated)
[Change] Remove stale intermediate vendored-lib sync ([Change] tlog_lib 2.0.5 etc) superseded by current 2.0.6 sync
[Change] v2.0.1 entry count 131 -> 112 (35 New / 24 Change / 53 Fix); CHANGELOG.RELEASE byte-parity preserved
…als + stage_started

[New] --json-report list active[]/stopped[] carry ppid + hashtype
      from scan.meta; reports[] carry derived completed_epoch
      (started_epoch + elapsed_seconds, null when inputs missing).
[Change] --json-report list field ordering normalized across all three
         arrays: identity -> process -> location -> time -> config
         -> metrics. No-consumer-currently contract allows reorder.
[Change] lmd_lifecycle.sh: remove dead stage_started meta-schema
         parser case and initializer (no code ever wrote the field
         to any meta file; parse case unreachable).
[Change] lmd_scan.sh, lmd_quarantine.sh: drop unused locals — _total
         in _wait_workers_with_progress, fname in restore,
         _filtered_sess and _ql in _batch_quarantine.
[New] tests/31-json-report.bats: tests 34-37 assert ppid/hashtype in
      active[]+stopped[], derived completed_epoch in reports[], and
      legacy-session completed_epoch parity.
…bled, sig_version parity

[New] _lmd_render_json_list top-level scanner {name, version,
      sig_version} and host {hostname, host_id} blocks mirror the
      per-scan JSON schema so consumers can parse invariants once
      instead of per-entry.
[Change] active[]/stopped[] field renames for per-scan JSON parity:
         hashtype → hash_type, hits → total_hits, elapsed →
         elapsed_seconds. Schema is unreleased (v2.x) so no deprecation
         shim needed.
[New] active[]/stopped[] add quarantine_enabled (boolean) from
      scan.meta; total_cleaned + total_quarantined emitted as null
      (not 0) to signal "not yet determined" mid-scan.
[New] stopped[] adds engine + sig_version for parity with active[].
[New] reports[] adds sig_version + quarantine_enabled; null for
      pre-bump 9-field index rows and legacy plaintext sessions.
[Change] scan.meta schema: new quarantine_enabled field written by
         _lifecycle_write_meta from global \$quarantine_hits; parser
         case added to _lifecycle_read_meta.
[Change] session.index schema bumped 9→11 fields (sig_version +
         quarantine_enabled as fields 10/11). _session_index_append
         variadic on trailing args (9-arg calls default to "-" / "0").
         _session_index_rebuild emits 11 fields from TSV headers.
         JSON and text readers tolerate old 8/9-field rows via the
         existing backward-compat shim.
[New] tests/31-json-report.bats: 9 new cases (38-46) for scanner/
      host blocks, schema-1.1 renames, quarantine_enabled boolean,
      null post-scan counters, sig_version parity, 9-field
      backward-compat.
[Change] tests/40-session-index.bats: append test updated for 11-field
         schema; new test covers default fields 10/11 when caller
         omits them.
…ed/stopped symmetry

[Change] Schema 1.1 consistency follow-up: stopped[] JSON field
         stopped_hr renamed to stopped, matching the suffix-less
         hr form already used by started (started/started_epoch pair
         becomes symmetric with stopped/stopped_epoch). Internal
         scan.meta and scan.checkpoint field names stay stopped_hr;
         only the user-facing JSON output field is renamed.
[Change] tests/31-json-report.bats test 32 assertion tightened from
         loose `--partial '"stopped_hr"'` (which would have matched
         the array opener `"stopped": [`) to a regex-anchored field
         match that requires `"stopped": "<hr string>"`.
[Change] _session_index_append: accept 14 args (was 11); 11-arg back-compat
         defaults completed_hr/engine/hash_type to "-"; emit #LMD_INDEX:v2
         header on file create
[Change] _session_index_rebuild: emit 14-field rows; wire through previously-
         discarded TSV vars _r_end_hr (col 8 → completed_hr), _r_hashtype
         (col 16 → hash_type), _r_engine (col 17 → engine); emit
         #LMD_INDEX:v2 header
[Change] _scan_finalize_session: pass _idx_completed_hr / _idx_engine /
         _idx_hashtype to _session_index_append; engine derivation mirrors
         _session_write_header (clamav vs native by $scan_clamscan)
[Test]   tests/40-session-index.bats: pre-flight TSV field-position fixture
         (Phase 1 work bundled into this commit) — asserts all 19 TSV header
         fields by name to catch positional drift before
         _session_index_rebuild reads the wrong column under schema 1.2
[Test]   tests/40-session-index.bats: 4 new tests for 14-arg writer round-
         trip, 11-arg back-compat default, v2 header emission, and mixed-
         version (9/11/14 field) coexistence; the Phase 1 pre-bump baseline
         test is renamed and flipped from "11 fields" to "14 fields" to
         match the new schema; existing assertions migrated to v2 header
         except the two back-compat seed tests that intentionally pin v1
         to verify rebuild/purge regression coverage
…sorb new fields

[Change] lmd_session.sh: text renderer reader (view_report list path) names
         _ix_completed_hr / _ix_engine / _ix_hashtype to prevent v1.2
         trailing fields from spilling into _ix_quar_enabled
[Change] lmd_alert.sh: _lmd_render_json_list pass-1 reader extends named-vars
         list with the same three v1.2 fields; pass-2 legacy reader
         unchanged (no session.index fields to consume — emits null in
         Phase 4)
[Test]   tests/31-json-report.bats: 3 new tests — 14-field row tolerance,
         mixed 9/11/14-field row coexistence, text path parity (no
         spillage)
…version 1.2

[Change] _lmd_render_json_list pass-1 emits "completed" / "engine" /
         "hash_type" for every report (string from index fields 12-14, or
         literal JSON null for pre-v1.2 rows missing those fields)
[Change] _lmd_render_json_list pass-2 (legacy plaintext fallback) emits
         literal JSON null for completed/engine/hash_type — plaintext
         session headers predate v1.2; null is the honest signal
[Change] _lmd_render_json_list top-level: "version": "1.1" → "schema_version":
         "1.2"; "type": "report_list" retained
[Test]   tests/31-json-report.bats: 4 new tests — schema_version key
         presence + old-key absence regression, fresh-scan reports[]
         carry the three new fields as JSON strings, legacy and 11-field
         rows emit literal JSON null
[Test]   tests/31-json-report.bats: migrate test 12 from "version 1.1"
         to "schema_version 1.2" — title and partial-match assertion
         updated together; without the migration the schema-version
         rename in _lmd_render_json_list silently breaks the existing
         passing test
[Change] _lmd_render_json: top-level shape refactored to
         {schema_version, scanner, host, reports[single]} matching
         _lmd_render_json_list; scan.id → reports[0].scan_id; hits and
         summary nested inside reports[0]; scanner block elevates engine +
         hash_type alongside name/version/sig_version; new per-scan fields
         started_epoch + completed_epoch (derived in bash via
         command date -d, passed to awk via -v for mawk portability)
[Change] _lmd_render_json_legacy: same shape mirror; per-hit fields keep
         existing null convention; reports[0].source="legacy" preserved;
         scanner.{engine,hash_type} emit null (legacy plaintext predates
         both)
[Test]   tests/31-json-report.bats: 6 new tests — exact top-level keyset
         assertion, scan_id rename guard, started_epoch/completed_epoch
         derivation parity, scanner.engine+hash_type elevation, legacy
         shape parity, view_report() asymmetric resolution preserved (JSON
         takes TSV first, text takes legacy first)
[Test]   tests/31-json-report.bats: migrated 7 existing assertions —
         6 assertions of '"version": "1.0"' rewritten to
         '"schema_version": "1.2"' (mechanical schema-version bump) AND
         1 assertion of '"id": "991231-2359.99999"' rewritten to
         '"scan_id": "991231-2359.99999"' to follow the
         scan.id → reports[0].scan_id rename in _lmd_render_json_legacy
…v1.2 refs

[Change] files/maldet.1: new JSON OUTPUT subsection under Reporting
         documenting the uniform top-level shape; --json-report
         description mentions schema 1.2 and the "newest" shorthand
[Change] files/maldet.1: session.index FILES entry describes 14-field v1.2
         layout; notes that #LMD_INDEX:v1 headers from prior installs are
         tolerated by all readers
[Change] README.md: JSON references updated from "v1.0 schema" to "v1.2
         schema" at lines 507 + 950; uniform-shape callout in §Integration
[Change] files/maldet: usage_long() --json-report updated to v1.2 schema
         reference; "newest" shorthand documented in option synopsis
…ion 1.2

[Fix] tests/31-json-report.bats test 1: title said "version 1.0"; assertion
      already correctly checks schema_version 1.2; title now matches
…rs toggle

[Fix] Ownership filters (scan_ignore_root/scan_ignore_user/scan_ignore_group)
      were applied unconditionally in monitor mode _monitor_cycle_tick(), causing
      root-owned malware to be silently dropped from the scan list — producing
      0 quarantine hits despite detection; issue #485
[Fix] Guard ownership filter arrays behind monitor_scan_owner_filters config var
      (default "0" = filters off, restores 1.6.6 production-safe behavior);
      when "1", filters are applied as in rc1..rc3 (opt-in)
[New] monitor_scan_owner_filters: new conf.maldet variable in [MONITORING
      OPTIONS] section with comment block explaining toggle semantics
[New] Add monitor_scan_owner_filters to _co allowlist in lmd_config.sh so
      -co monitor_scan_owner_filters=1 is accepted at CLI
[New] 4 regression tests in 30-monitor-utils.bats (21→25 total): default
      toggle admits root-owned files, scan_ignore_user=root suppressed under
      default, toggle=1 restores filter, extension filters preserved
[Change] Emit eout log line when _event_count > 0 but _tot_files == 0
         (every queued event dropped by tier-2 size/ext/perm/owner find
         filter) so operators can self-diagnose silent no-scan cycles
         without enabling verbose logging.
…485

[Change] tests/helpers/uat-lmd.bash: remove redundant
        scan_ignore_root="0" reset from uat_lmd_install (already
        baked by tests/Dockerfile*); add opt-in
        uat_lmd_disable_root_filter helper so each UAT caller
        explicitly documents its dependency on the override
        (effective value unchanged — Dockerfile bake remains);
        issue #485
[Change] tests/uat/*.bats: migrate 19 callers to invoke
        uat_lmd_disable_root_filter after uat_lmd_install,
        preserving prior test semantics; issue #485
#485

[New] tests/uat/05-monitor-mode.bats: @test asserting monitor
      quarantines a root-owned EICAR in a webuser-owned docroot
      under scan_ignore_root=1 (explicit re-set overrides Dockerfile
      bake); regression test closes the coverage hole that let the
      tier-2 ownership filter regression ship through rc1..rc3;
      issue #485
[Change] conf.maldet: scan_ignore_root comment cross-references monitor
         carve-out (monitor_scan_owner_filters default-off); issue #485
[Change] README.md: scan_ignore_root row notes monitor exception; new
         monitor_scan_owner_filters row in monitor config table; issue #485
[Change] maldet.1: MONITOR MODE gains carve-out paragraph explaining
         default-off ownership filters and monitor_scan_owner_filters
         semantics; Monitoring config group listing updated; issue #485
[Fix] 30-monitor-utils.bats: monitor_scan_owner_filters unit tests used
      "${_owner_filter_args[@]}" and "${ignore_root[@]}" etc. inside
      _source_lmd_stack's set -eu scope; Bash 4.2 (CentOS 7) treats
      "${arr[@]}" of an empty array as unbound under set -u, causing
      4 test failures. Replace with Bash 4.2-safe idiom:
      ${arr+"${arr[@]}"} (expands to nothing when arr is empty,
      expands normally when arr has elements). Tests 88-91 now
      pass on all 9 OS targets; issue #485.
…ignore-inotify-defaults

[Change] 01-install-cli.bats: remove "uninstall.sh delegates service
         removal to pkg_service_uninstall" — tautological grep-for-token
         assertion, proves string presence but not behavior
[Change] 47-ignore-inotify-defaults.bats: remove "ignore_inotify.defaults
         is installed at internals path" — adjacent "has non-comment
         entries" test implicitly requires the file to exist (grep -c
         on a missing path exits non-zero), so the existence assertion
         is redundant coverage
…; issue #484

[Fix] _monitor_load_ignore_inotify_union: emit u:/d: prefixed tuples
[Fix] monitor_cycle exclude-regex builder: per-entry semantic dispatch via
      _monitor_to_ere_entry — user entries raw ERE by default, literal:
      prefix opts into escape; defaults always escape
[Fix] _monitor_filter_events: drop ignore-file arg; ignore_paths filtering
      moves to sibling grep -E -vf stage (ERE, matches scan-mode)
[New] _monitor_to_ere_entry(): semantic dispatch helper
[Change] CHANGELOG, CHANGELOG.RELEASE: v2.0.1 [Fix] + [New] entries
[Change] tests/30-monitor-utils.bats: +10 tests (7 _monitor_to_ere_entry,
      3 union: prefix); rewrite ERE pipe test; drop stale ignore-file arg
      from 5 sibling _monitor_filter_events tests
[Change] tests/47-ignore-inotify-defaults.bats: update 4 existing union
      tests to expect u:/d: prefixed output (contract change per spec
      §11b edge case 3)
…#484

[Fix] Add literal "scantemp." substring (trailing dot prevents user-file FP
      like scantemplate.php) — restores the noise suppression dropped in
      commit 984c0b1 (issue #480 sentinel fixup)
[Change] tests/47-ignore-inotify-defaults.bats: +2 sentinel tests
[Change] CHANGELOG, CHANGELOG.RELEASE: v2.0.1 [Fix] entry
…on; issue #484

[New] tests/48-monitor-ignore-regex.bats: 11 integration tests covering
      user regex passthrough, literal: prefix escape, defaults auto-escape,
      ignore_paths ERE consistency with scan-mode, malformed-regex
      symmetry (ignore_inotify + ignore_paths), and the sentinel
      against the misleading comment at lmd_monitor.sh:74
[Change] CHANGELOG, CHANGELOG.RELEASE: v2.0.1 [Change] entry
…; issue #484

[Change] files/ignore_inotify: header rewrite — ERE default, literal: opt-in,
      mixed examples (regex + literal + plain)
[Change] README.md §7 Monitor Mode: Ignore files paragraph corrected
[Change] files/maldet.1 FILES section: ignore_inotify description aligned
[Change] CHANGELOG, CHANGELOG.RELEASE: v2.0.1 [Change] entry
…lters

[Change] files/conf.maldet: strip "(matches rc1..rc3 behavior)" parenthetical
         from monitor_scan_owner_filters description; rc tags are pre-release
         artifacts not meaningful to end users
[Change] README.md: same trim in config reference table
[Change] CHANGELOG, CHANGELOG.RELEASE: align v2.0.1 entry for byte-parity
[Change] files/internals/{lmd.lib,lmd_alert,lmd_clamav,lmd_config,
         lmd_engine,lmd_hook,lmd_init,lmd_lifecycle,lmd_monitor,
         lmd_quarantine,lmd_scan,lmd_session,lmd_sigs,lmd_update,
         lmd_yara}.sh: strip LMD_<STEM>_VERSION="1.0.0" assignments +
         paired shellcheck disable=SC2034 directives (45 deletions);
         constants were declared at sub-lib extraction but never read
         from any source, test, packaging, or doc surface
[Change] _LMD_<STEM>_LOADED=1 source guards untouched — those remain
         the idempotent-sourcing mechanism; _VERSION constants were
         parallel, not load-bearing
[Change] CHANGELOG, CHANGELOG.RELEASE: v2.0.1 [Change] entry;
         round 3 AI-slop audit
@rfxn rfxn merged commit 88dd694 into master Apr 26, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants