Skip to content

Release v2.6.0

Choose a tag to compare

@cristi-boboc cristi-boboc released this 19 Apr 21:05
· 4831 commits to main since this release

Added

  • Optional Sentry crash reporting via a new internal/obs package wrapping sentry-go. A sentry: config block (enabled, dsn, environment, sample_rate, debug) toggles it; disabled or empty-DSN are no-ops. Long-lived daemon goroutines run through obs.Go/obs.SafeGo so panics land in Sentry with component/os/panel/webserver tags. DSN is redacted from csm config show.

Changed

  • YARA-X 1.14.0 -> 1.15.0 on the glibc-2.28 builder. Go binding API unchanged. CSM_BUILDER_TAG stepped to glibc-2.28-r2. The 1.15.0 attempt on the previous Alpine+musl-static builder crashed in yrx_compiler_build; the move to glibc-dynamic clears that.

Added

  • Emailav YARA-X adapter works under worker mode, closing ROADMAP item 2's last sub-item. yaraipc.Match and yara.Match gained Meta map[string]string (string-valued rule metadata); emailav.YaraXScanner reads severity from Meta["severity"] via yara.Backend instead of crossing the process boundary. Daemon and email-API UI switched from yara.Global() to yara.Active() for consistent behaviour under either backend.
  • ROADMAP entries 4-9: Prometheus endpoint, audit log export, bbolt retention, SIGHUP hot-reload, baseline/state backup+restore, and challenge UX polish (CAPTCHA fallback, verified-session bypass, verified-crawler allow-pass).
  • Internal csm yara-worker subcommand and internal/yaraipc wire protocol (length-prefixed JSON over Unix socket). Groundwork for moving YARA-X into a supervised child process so a cgo crash cannot take the daemon down (ROADMAP item 2); still in-process this release.
  • yaraworker.Supervisor fork+execs the worker with exponential-backoff restart, readiness Ping, in-process Reload, and an escalation RestartWorker. Gated by signatures.yara_worker_enabled (default off); crashes emit a Critical yara_worker_crashed finding rate-limited to one per minute. Scan callers route through yara.Active().
  • internal/metrics package: Counter, Gauge, Histogram, their labelled vector variants, and a Registry that renders Prometheus text exposition format. Zero external deps. Groundwork for ROADMAP item 4.
  • /metrics endpoint on the web UI (ROADMAP item 4 complete). Auth accepts cfg.WebUI.MetricsToken as a dedicated Bearer and falls back to UI AuthToken / session cookie. Eleven metrics shipped (csm_build_info, csm_yara_worker_restarts_total, csm_findings_total, csm_store_size_bytes, three csm_fanotify_* series, csm_check_duration_seconds with {name,tier} labels, csm_blocked_ips_total, csm_firewall_rules_total, csm_auto_response_actions_total). Documented in docs/src/metrics.md.
  • Metrics hardening: per-subsystem smoke tests so a dropped observe* call site fails CI instead of surviving a manual scrape. New docs/src/examples/prometheus-scrape.yml plus a promtool-check CI job that validates it.
  • docs/src/configuration.md "Editing csm.yaml by hand" section: documents the csm rehash step between a manual edit and systemctl restart csm. integrity.Verify refuses any config whose sha256 disagrees with cfg.Integrity.ConfigHash and crash-loops the daemon on restart, so the section covers backup / edit / rehash / validate / restart / rollback.
  • SIGHUP config hot-reload (ROADMAP item 7, initial). internal/config gains Active()/SetActive() over atomic.Pointer[Config] and a Diff() that classifies top-level fields via a new hotreload struct tag. Fields tagged safe swap in place; untagged or restart-tagged fields abort the reload with a Warning config_reload_restart_required finding naming the offending fields, and the live config stays untouched. Thresholds is the first safe field. Parse/validation errors emit a Critical config_reload_error and keep the old config live. Reload re-signs integrity.config_hash in place. ExecReload=/bin/kill -HUP $MAINPID wired into the systemd unit. Known caveat: four long-lived tickers (deep_scan_interval_min, wp_core_check_interval_min, webshell_scan_interval_min, filesystem_scan_interval_min) keep firing at the old interval until restart; every other threshold takes effect next tick.

Fixed

  • False-positive quarantine storm on WP auto-updates and WPML. Deep-scan for /wp-content/languages/ and /wp-content/upgrade/ now runs content analysis first (matching the realtime path), the WPML translation queue is recognised as known-safe, and the auto-quarantine entropy floor is raised from 4.8 to 5.5 (WPML and Breakdance added to the library-path allowlist). A 2026-04-17 incident quarantined 109 benign files; the same content no longer trips any gate.
  • Realtime php_in_sensitive_dir_realtime Warning no longer fires on WPML translation-queue regenerations. The fanotify path now shares checks.IsSafePHPInWPDir with the polled deep-scan, so a language-pack refresh no longer produces 30-40 Warnings per second.
  • Realtime fanotify tests in internal/daemon stopped feeding os.File.Fd() into analyzer functions (analyzeFile, checkCrontab, checkPHPContent, checkHtaccess, checkUserINI, checkHTMLPhishing, checkCGIBackdoor). That pattern attached the netpoller and a GC finalizer to fds the tests then handed off to syscalling code, producing an intermittent EBADF under -race + coverage (CI job 93822). 11 test files converted to raw unix.Open/unix.Close fds, matching the production ownership model.
  • WebUI "YARA RULES" card and "Reload Rules" button work under worker mode. The rules API was reading yara.Global() (nil when the worker is on, so the dashboard reported 0); it now uses yara.Active(), covering both backends.
  • runPeriodicChecks no longer fires a spurious Critical integrity tamper alert on every tier tick after a successful SIGHUP reload. integrity.Verify was passed the startup d.cfg whose ConfigHash went stale as soon as reload re-signed the on-disk file; it now runs against d.currentCfg(). Regression: TestReloadConfigIntegrityVerifyPassesAfterReload.
  • SIGHUP reload no longer misclassifies every reload as restart_required from a false-positive firewall diff. startFirewall was merging cfg.InfraIPs into d.cfg.Firewall.InfraIPs at boot, so a fresh post-reload config (without that merge) diffed non-equal even on an unedited file. The merge now uses a shallow-copied FirewallConfig and leaves d.cfg.Firewall untouched. Caught on live production smoke (2026-04-19). Regression: TestDiffLoadLoadIsEmpty.
  • A SIGHUP reload rejected as restart_required no longer leaves the on-disk file with a stale integrity.config_hash that would crash-loop the daemon on next restart. The handler now re-signs the file via integrity.SignAndSaveAtomic on that branch and updates the live config.Active() ConfigHash in lock-step, so between edit and restart integrity.Verify(currentCfg) does not see a disk/memory divergence. Also caught on live production smoke. Regression: TestReloadConfigRestartRequiredKeepsIntegrityConsistent.
  • firewall: netlink receive: recvmsg: no buffer space available on startup and every SIGHUP. Engine.loadState was issuing one SetAddElements call per persisted entry, overflowing the netlink socket's SO_RCVBUF on hosts with a few hundred blocks. Entries are now collected per target set and shipped via a new addElementsChunked helper that caps each message at 1000 elements (~28 KB, below the 208 KB rmem default); 1000 is even so interval sets never split a {start, IntervalEnd} pair.
  • Firewall resolveSubnetSet and loadState's blocked_net loop skip cleanly when lastIPInRange returns nil (malformed net.IPNet whose IP is neither 4 nor 16 bytes). Prior code fell through to nextIP(nil) and fed an empty Key to the kernel. Regression: TestResolveSubnetSetMalformedIPReturnsNil.
  • plugincheck no longer double-logs one hung wp-cli call as both Command timed out and JSON parse failed: unexpected end of JSON input, and stderr chatter (PHP warnings, MySQL deprecation notices, broken-plugin backtraces) can no longer poison the JSON (the invalid character 'W'/'P'/'N' class). wp-cli runs through a new stdout-only CmdRunner.RunContextStdout that surfaces context.DeadlineExceeded, plus --skip-plugins --skip-themes and WP_CLI_PHP_ARGS='-d display_errors=0 -d error_reporting=0' so one broken plugin can't tip the whole enumeration into exit 255. Per-site failure lines replaced by a single refresh summary with timeout=N exec_fail=N json_fail=N counters.

Changed

  • SIGHUP hot-reload safe-reload set grows from one field to six: alerts, suppressions, auto_response, reputation, and email_protection are now tagged hotreload:"safe" alongside thresholds. Auto-response paths, alert.Dispatch, and the heartbeat read d.currentCfg() per call; batch handlers (dispatchBatch, initial-scan) snapshot once at the top so a reload landing mid-batch never splits a finding set between policies. Regression: TestDiffAllSafeFieldsClassifiedSafe.

  • csm run-deep no longer exits with reading response: i/o timeout on large servers. Tier-run RPCs get a 60-minute deadline (plugin-cache refresh fans out a wp-cli per site); other CLI commands keep the 5-minute default. The hourly csm-deep.service timer now logs a real tier=deep findings=X new=Y elapsed_ms=Z line instead of failing every hour.

  • db_post_injection stopped firing on legacy author-embedded <script src="http://..."> tags. Post-content URL classification uses a post-specific predicate that drops the plaintext-HTTP indicator but keeps structural markers (raw IP, abused TLD, known-bad exfil host, invalid host). wp_options classification is unchanged.

  • suspicious_php_content no longer fires purely on "shell function co-present with request input" against WordPress file-manager plugins (FileOrganizer, elFinder). The co-presence signal is corroboration only; same-line shell-function-with-request-input still fires alone. containsStandaloneFunc also rejects method calls, static calls, and function declarations (e.g. an SQLite driver's $this->DB->...(...) line with a $_SERVER reference no longer trips the same-line rule).

  • webshell_p0wny dropped the bare "p0wny" pattern. Combined with the p0wny.?shell regex it double-counted on a single occurrence of "p0wny-shell" (e.g. a docblock reference), clearing min_match: 2 on its own. The remaining patterns (featureShell, makeCommand, window.term) are structural markers unique to the shell's terminal UI.

  • AbuseIPDB quota enforcement persists across 10-minute cycles: a 429/402 backs off until the next UTC midnight, a per-day counter (cap 900) acts as a circuit breaker, and transient-error cache entries expire at the intended ~1h (the pre-fix formula shifted CheckedAt into the future, effectively stretching error caches to ~11h).