Skip to content

Add vulnerability email de-duplication integration#103

Open
leonfullxr wants to merge 3 commits into
mainfrom
vulnerability-email-dedup
Open

Add vulnerability email de-duplication integration#103
leonfullxr wants to merge 3 commits into
mainfrom
vulnerability-email-dedup

Conversation

@leonfullxr

Copy link
Copy Markdown
Member

Summary

Adds integrations/vulnerability_email_dedup/, a drop-in replacement for a custom-email integration that suppresses duplicate vulnerability notifications.

The Vulnerability Detection module emits one alert per (package, CVE) pair. A single package often carries dozens of CVEs, so one scan produces a burst of near-identical alerts for the same package on the same agent. Routed to email through the stock integration, that burst becomes hundreds of near-identical messages.

This integration sends one email per affected package per agent instead of one per CVE. Every subsequent CVE for the same key inside the suppression window is recorded and counted, but not mailed. Non-vulnerability alerts pass through unchanged.

Design notes

  • Execution model. wazuh-integratord runs the script once per alert as a separate process, so a 1000-alert burst is 1000 short-lived processes rather than one process handling 1000 items. Correctness under the burst comes from the storage layer, not from concurrency inside the script.
  • Concurrency. State lives in a SQLite store in WAL mode with a busy timeout. The check-and-write runs inside a single BEGIN IMMEDIATE transaction, so concurrent processes for the same package serialize on the write lock and exactly one wins.
  • Hot path. Suppressed alerts exit on one indexed lookup and never open an SMTP connection. smtplib is imported lazily so the suppression path does not pay the import cost.
  • Window anchoring. The suppression window is measured from the first notification, not from the last alert seen. Anchoring on the last alert would let a steady drip of CVEs slide the window forward indefinitely and silence a package permanently.
  • Fail open. If the store cannot be opened or the de-duplication check raises, the alert is emailed. A duplicate is preferable to a dropped notification.
  • Maintenance. Cleanup is an explicit prune mode driven by a command wodle rather than opportunistic work on the alert hot path. The table is bounded by distinct (agent, package, status) combinations, not by alert volume, so this is housekeeping rather than growth control.

Files

  • custom-email.py - integration logic, Python standard library only.
  • custom-email - standard Wazuh shell wrapper for the embedded interpreter.
  • README.md - installation, configuration table, ossec.conf blocks for both the integration and the prune wodle, testing steps, troubleshooting, provenance.

Testing

Verified end to end against a local SMTP sink on Python 3.11:

  • First alert for a package sends one email; a second CVE for the same agent and package is suppressed and increments cve_count.
  • Non-vulnerability alerts are emailed unchanged.
  • Ageing a row past the window re-notifies, confirming recurring problems stay visible.
  • 2,000-alert burst across 300 distinct (agent, package, status) combinations at 64-way concurrency: 300 emails sent, one per distinct key, an 85% reduction. SUM(cve_count) equalled 2,000, confirming no lost updates. No lock errors, ~665 alerts/sec.
  • prune mode runs and reports cleanly.

Not yet exercised against a live vulnerability scan on a production manager; the ossec.conf wiring in the README is the documented path for that.

The Vulnerability Detection module emits one alert per (package, CVE) pair.
A single package often carries dozens of CVEs, so one scan produces a burst
of near-identical alerts for the same package on the same agent, which the
stock email integration turns into hundreds of near-identical messages.

This integration collapses the burst: the first alert for a given
(agent, package, status) sends one email, and every subsequent CVE for that
same key inside the suppression window is counted but not mailed.
Non-vulnerability alerts pass through unchanged.

State is kept in a SQLite store in WAL mode, with the check-and-write done in
a single BEGIN IMMEDIATE transaction so the ~1000 concurrent integratord
processes of a burst serialize correctly without lost updates or lock errors.
Suppressed alerts exit on one indexed lookup and never open an SMTP
connection. The suppression window is anchored at the first notification
rather than the last alert seen, so a steady drip of CVEs cannot postpone the
window indefinitely and silence a package. Cleanup is an explicit prune mode
driven by a command wodle, keeping maintenance off the alert hot path.
@leonfullxr leonfullxr self-assigned this Jul 18, 2026
@leonfullxr
leonfullxr marked this pull request as draft July 18, 2026 17:05
@leonfullxr
leonfullxr requested a review from Copilot July 18, 2026 17:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Wazuh integration (integrations/vulnerability_email_dedup/) that acts as a drop-in custom-email replacement to suppress duplicate Vulnerability Detection emails by de-duplicating on (agent_id, package, status) within a fixed time window, while still passing non-vulnerability alerts through unchanged.

Changes:

  • Introduces a SQLite/WAL-backed de-duplication store to suppress duplicate vulnerability emails during bursts.
  • Adds a prune maintenance mode intended to be run via a scheduled Wazuh command wodle.
  • Provides installation/configuration/testing documentation and a standard Wazuh shell wrapper.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
integrations/vulnerability_email_dedup/README.md Documents the integration behavior, configuration, ossec.conf wiring, and testing/maintenance steps.
integrations/vulnerability_email_dedup/custom-email.py Implements per-alert de-duplication logic with SQLite WAL and a prune maintenance entry point.
integrations/vulnerability_email_dedup/custom-email Provides the standard Wazuh wrapper to invoke the Python script via the embedded interpreter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread integrations/vulnerability_email_dedup/custom-email.py Outdated
Comment thread integrations/vulnerability_email_dedup/README.md Outdated
Comment thread integrations/vulnerability_email_dedup/custom-email.py Outdated
Comment thread integrations/vulnerability_email_dedup/custom-email.py
Comment thread integrations/vulnerability_email_dedup/custom-email.py
Comment thread integrations/vulnerability_email_dedup/custom-email.py
- Fall back to stderr logging when LOG_PATH is not writable, so a logging
  problem cannot stop the integration from sending mail.
- Exit non-zero on prune failure and on email send failure, so failures are
  visible to the scheduler / integrator instead of appearing successful.
- Clarify (docstring + README) that the package version is read from the
  triggering alert for the email body and is not persisted.
@leonfullxr
leonfullxr requested a review from Copilot July 21, 2026 08:36
@leonfullxr
leonfullxr marked this pull request as ready for review July 21, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread integrations/vulnerability_email_dedup/custom-email.py
Comment thread integrations/vulnerability_email_dedup/custom-email.py
Comment thread integrations/vulnerability_email_dedup/custom-email.py
- Strip CR/LF from email header values (the subject is built from alert-derived
  package/agent names) to prevent header injection and send-time crashes.
- Close the SQLite connection if a PRAGMA/DDL fails during open_db, so a failed
  init does not leak the handle.
- Add an explicit no-args usage check for a clearer log message (the IndexError
  was already caught by the existing handler; this only improves diagnostics).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread integrations/vulnerability_email_dedup/custom-email.py
Comment thread integrations/vulnerability_email_dedup/custom-email.py
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