Enriching Wazuh vulnerability detection with Google Gemini integration

| by | Wazuh 4.14.4
Post icon

Organizations constantly struggle with vulnerabilities affecting operating systems, applications, and third-party software. These weaknesses expand the attack surface and can be exploited by attackers to compromise the confidentiality, integrity, or availability of systems.

Wazuh offers vulnerability detection capability that identifies vulnerabilities in systems and software. However, security analysts must also determine a vulnerability’s exploitability, potential impact, and remediation urgency. This often requires manual research across vulnerability databases and security advisories. Artificial intelligence can streamline this process by automatically analyzing vulnerability context and providing actionable insights to support faster vulnerability triage.

In this blog post, we integrate Google Gemini with the Wazuh vulnerability detection capability to enrich vulnerability alerts with contextual security insights. The integration provides contextual explanations of vulnerability exploitability, potential impact, and remediation urgency directly within the Wazuh dashboard.

Infrastructure

We use the following infrastructure for the integration of Wazuh with Google Gemini:

  • A pre-built, ready-to-use Wazuh OVA 4.14.3, which includes the Wazuh central components (Wazuh server, Wazuh indexer, and Wazuh dashboard). Follow this guide to download and set up the Wazuh virtual machine.
  • A Windows 11 endpoint with Wazuh agent 4.14.3 installed and enrolled in the Wazuh server. This endpoint is required to simulate a vulnerability detection alert.
  • A Google account.

Vulnerability detection

The Wazuh agent collects system inventory data, including the operating system, installed packages, and software versions from monitored endpoints. This inventory is periodically sent to the Wazuh server for analysis. The Wazuh server correlates the collected data with vulnerability intelligence from the Wazuh Cyber Threat Intelligence (CTI) platform to identify packages affected by known vulnerabilities. The findings are stored in a vulnerability index (wazuh-states-vulnerabilities-*) in the Wazuh indexer, and alerts are generated on the Wazuh dashboard. 

To view vulnerability alerts generated on the Wazuh dashboard, click on the upper left menu and navigate to Threat intelligence > Vulnerability detection > Events. Click on Explore agent and select the Windows endpoint.

Wazuh Dashboard

The alert contains details such as the CVE identifier, severity level, vulnerability rationale, affected package, installed version, and reference links. Click on Inspect document details to see the alert details.

Wazuh Dashboard

To assist with the vulnerability triage, we integrate Google Gemini with Wazuh so that vulnerability alerts can be automatically enriched with concise contextual insights. This integration allows analysts to receive summarized explanations, remediation guidance, and patch urgency recommendations directly within the Wazuh environment.

Google Gemini integration

We configure the Wazuh server to forward vulnerability detection alerts to Google Gemini using a custom Python script. The script extracts key vulnerability fields such as the CVE ID, package name, installed version, and CVSS score, and sends them to the Gemini API for contextual enrichment.

Generating a Gemini API key

Follow the steps below to generate an API key from Google AI Studio.

  1. Open Google AI Studio and sign in with your Google account.
  2. Create a new project.
    1. Navigate to Dashboard > Projects and click on Create a new project.
    2. Assign a name for the project, and select Create project to create a new project.
Google AI Studio
  1. Create a Gemini API key.
    1. Navigate to Get API key and click on Create API key.
    2. Assign a name for the API key, and select the project created in the previous step. 
    3. Click Create key to generate the API key. The API key is used to identify and authenticate the Wazuh manager to Gemini.
Google AI Studio

Configuring the integration

Wazuh server

Perform the following steps on the Wazuh server to configure the integration.

  1. Run the command below to install the requests Python library required by the integration script:
# /var/ossec/framework/python/bin/pip3 install requests
  1. Create the custom integration script custom-gemini.py in the /var/ossec/integrations directory and add the following content to it.
Warning: This script is a proof of concept (PoC). Review and validate it to ensure it meets your environment’s operational and security requirements.
#!/var/ossec/framework/python/bin/python3

import json
import sys
import time
import os
from socket import socket, AF_UNIX, SOCK_DGRAM

try:
    import requests
except Exception:
    print("No module 'requests' found. Install: pip3 install requests")
    sys.exit(1)

# ---------- Config ----------
debug_enabled = False
pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
log_file = f'{pwd}/logs/integrations.log'
socket_addr = f'{pwd}/queue/sockets/queue'
now = time.strftime("%a %b %d %H:%M:%S %Z %Y")

# Gemini API config
GEMINI_MODEL = "gemini-2.0-flash"
GEMINI_ENDPOINT = f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL}:generateContent"

# ---------- Helpers ----------
def debug(msg):
    if debug_enabled:
        msg = f"{now}: {msg}\n"
    print(msg)
    try:
        with open(log_file, "a") as f:
            f.write(str(msg))
    except Exception:
        pass  # avoid breaking on logging errors

def send_event(msg, agent=None):
    if not agent or agent.get("id") == "000":
        string = '1:gemini:{0}'.format(json.dumps(msg))
    else:
        string = '1:[{0}] ({1}) {2}->gemini:{3}'.format(
            agent.get("id"),
            agent.get("name"),
            agent.get("ip") if "ip" in agent else "any",
            json.dumps(msg),
        )
    debug(string)
    sock = socket(AF_UNIX, SOCK_DGRAM)
    sock.connect(socket_addr)
    sock.send(string.encode())
    sock.close()

def first(items, default=""):
    if isinstance(items, list) and items:
        return items[0]
    return default

# ---------- Prompting ----------
def build_prompt(alert):
    """
    Vulnerability-focused prompt builder for Wazuh vulnerability-detector alerts.
    Agent context intentionally removed for data minimization.
    """

    # --- Core Wazuh rule fields ---
    rule = alert.get("rule", {}) or {}
    location = alert.get("location") or ""

    # --- Vulnerability payload ---
    data = alert.get("data", {}) or {}
    vuln = data.get("vulnerability", {}) or {}

    cve = vuln.get("cve") or ""
    severity = vuln.get("severity") or ""
    title = vuln.get("title") or ""
    rationale = vuln.get("rationale") or ""
    published = vuln.get("published") or ""
    updated = vuln.get("updated") or ""
    status = vuln.get("status") or ""
    vendor_ref = vuln.get("reference") or ""
    scanner_ref = (vuln.get("scanner", {}) or {}).get("reference", "") or ""

    pkg = vuln.get("package", {}) or {}
    pkg_name = pkg.get("name") or ""
    pkg_version = pkg.get("version") or ""
    pkg_source = pkg.get("source") or ""
    pkg_arch = pkg.get("architecture") or ""
    fix_condition = pkg.get("condition") or ""

    # --- CVSS extraction ---
    cvss_base = ""
    score = vuln.get("score", {}) or {}
    if score.get("base") is not None:
        cvss_base = str(score.get("base"))
    else:
        cvss3 = (vuln.get("cvss", {}) or {}).get("cvss3", {}) or {}
        if cvss3.get("base_score") is not None:
            cvss_base = str(cvss3.get("base_score"))

    cvss3 = (vuln.get("cvss", {}) or {}).get("cvss3", {}) or {}
    vector = cvss3.get("vector", {}) or {}

    # --- Build structured vulnerability context ---
    bits = []
    bits.append("This is a Wazuh vulnerability-detector alert.")

    # Rule context (kept minimal)
    if rule.get("id") or rule.get("level") or rule.get("groups"):
        groups = ", ".join((rule.get("groups") or [])[:6])
        bits.append(
            f"Rule: id={rule.get('id')}, level={rule.get('level')}, groups=[{groups}]"
        )

    if location:
        bits.append(f"Location: {location}")

    # Vulnerability essentials
    if title:
        bits.append(f"Title: {title}")
    if cve:
        bits.append(f"CVE: {cve}")
    if severity:
        bits.append(f"Severity: {severity}")
    if cvss_base:
        bits.append(f"CVSS base score: {cvss_base}")
    if status:
        bits.append(f"Status: {status}")

    # Package details
    if pkg_name:
        bits.append(f"Affected package: {pkg_name}")
    if pkg_version:
        bits.append(f"Installed version: {pkg_version}")
    if pkg_source:
        bits.append(f"Package source: {pkg_source}")
    if pkg_arch:
        bits.append(f"Architecture: {pkg_arch}")
    if fix_condition:
        bits.append(f"Fix condition: {fix_condition}")

    if rationale:
        bits.append(f"Rationale: {rationale[:600]}")

    if published:
        bits.append(f"Published: {published}")
    if updated:
        bits.append(f"Updated: {updated}")

    if vendor_ref:
        bits.append(f"Vendor reference: {vendor_ref}")
    if scanner_ref:
        bits.append(f"Scanner reference: {scanner_ref}")

    # CVSS vector details
    if vector:
        ordered = [
            "attack_vector",
            "privileges_required",
            "user_interaction",
            "scope",
            "confidentiality_impact",
            "integrity_impact",
            "availability",
        ]
        vec_lines = []
        for k in ordered:
            if k in vector and vector[k] is not None:
                vec_lines.append(f"- {k}: {vector[k]}")
        if vec_lines:
            bits.append("CVSS vector details:\n" + "\n".join(vec_lines))

    context = "\n".join(bits)

    # --- Final SOC-focused prompt ---
    prompt = (
        "You are assisting security engineers and SOC analysts with vulnerability triage.\n"
        "Based ONLY on the data provided:\n"
        "1) Explain what the vulnerability is and what it enables.\n"
        "2) Summarize exploitation prerequisites using the CVSS vector details, if present.\n"
        "3) Provide a patch urgency recommendation (Critical/High/Medium/Low) with justification.\n"
        "4) Provide concrete remediation steps and validation checks.\n"
        "Write 4–6 concise sentences. Do not invent exposure assumptions or exploit activity not stated.\n\n"
        f"{context}"
    )

    return prompt
# ---------- Gemini ----------
def call_gemini(prompt, api_key):
    headers = {
        "Content-Type": "application/json",
        "x-goog-api-key": api_key,
    }
    payload = {
        "contents": [
            {
                "parts": [
                    {"text": prompt}
                ]
            }
        ]
    }
    try:
        resp = requests.post(GEMINI_ENDPOINT, headers=headers, json=payload, timeout=30)
    except requests.RequestException as e:
        return {"error": "transport_error", "description": str(e)}

    if resp.status_code != 200:
        try:
            j = resp.json()
            desc = json.dumps(j)[:2048]
        except Exception:
            desc = resp.text[:2048]
        return {"error": str(resp.status_code), "description": desc}

    try:
        j = resp.json()
        text = ""
        resp_id = j.get("responseId")
        model_ver = j.get("modelVersion")
        if "candidates" in j and j["candidates"]:
            content = j["candidates"][0].get("content", {})
            parts = content.get("parts", [])
            if parts and isinstance(parts, list):
                text = parts[0].get("text", "") or ""
        return {
            "text": text.strip(),
            "response_id": resp_id,
            "model_version": model_ver,
            "raw": j,  # keep for debug; you can remove in production
        }
    except Exception as e:
        return {"error": "parse_error", "description": str(e)}

# ---------- Core ----------
def build_output(alert, prompt_used, gemini_result):
    rule = alert.get("rule", {}) or {}
    vuln = (alert.get("data", {}) or {}).get("vulnerability", {}) or {}

    scanner_ref = (vuln.get("scanner", {}) or {}).get("reference")
    vendor_ref = vuln.get("reference")
    cve = vuln.get("cve")
    severity = vuln.get("severity")

    out = {
        "integration": "custom-gemini",
        "gemini": {
            "found": 0,
            "source": {
                "alert_id": alert.get("id"),
                "rule": rule.get("id"),
                "description": rule.get("description"),
                "level": rule.get("level"),
                "groups": rule.get("groups"),
                "mitre": rule.get("mitre"),
                "location": alert.get("location"),
                "timestamp": alert.get("timestamp"),
                "cve": cve,
                "severity": severity,
                "reference": scanner_ref,
                "vendor_reference": vendor_ref
            },
            "prompt_used": prompt_used[:2000]
        }
    }

    if gemini_result.get("error"):
        out["gemini"]["error"] = gemini_result["error"]
        out["gemini"]["error_description"] = gemini_result.get("description")
        return out

    text = gemini_result.get("text", "")
    if text:
        out["gemini"]["found"] = 1
        out["gemini"]["summary"] = text
        meta = {}
        if gemini_result.get("response_id"):
            meta["response_id"] = gemini_result["response_id"]
        if gemini_result.get("model_version"):
            meta["model_version"] = gemini_result["model_version"]
        if meta:
            out["gemini"]["model_meta"] = meta
    else:
        out["gemini"]["note"] = "Empty or blocked response from model."

    return out

def process_alert(alert, api_key):
    prompt = build_prompt(alert)
    result = call_gemini(prompt, api_key)
    return build_output(alert, prompt, result)

# ---------- Entrypoint ----------
def main(args):
    debug("# Starting custom-gemini (rule.description) integration")
    if len(args) < 3:
        debug("# Exiting: Bad arguments.")
        sys.exit(1)

    alert_file = args[1]
    api_key = args[2]
    debug("# API key (masked)")
    debug((api_key[:5] + "...") if api_key else "MISSING")
    debug("# Alert file")
    debug(alert_file)

    with open(alert_file, "r") as f:
        alert = json.load(f)

    msg = process_alert(alert, api_key)
    if msg:
        send_event(msg, alert.get("agent"))

if __name__ == "__main__":
    try:
        if len(sys.argv) >= 4:
            # argv: [1]=alert.json, [2]=API_KEY, [3]=extra?, [4]=debug
            _msg = '{0} {1} {2} {3} {4}'.format(
                now,
                sys.argv[1],
                sys.argv[2],
                sys.argv[3] if len(sys.argv) > 3 else "",
                sys.argv[4] if len(sys.argv) > 4 else ""
            )
            debug_enabled = (len(sys.argv) > 4 and sys.argv[4] == 'debug')
        else:
            _msg = f'{now} Wrong arguments'
        with open(log_file, 'a') as f:
            f.write(str(_msg) + '\n')

        if len(sys.argv) < 3:
            sys.exit(1)

        main(sys.argv)
    except Exception as e:
        debug(str(e))
        # Avoid crashing analysisd on unexpected exceptions.
  1. Run the command below to update the permissions and ownership of the /var/ossec/integrations/custom-gemini.py script:
# chmod 750 /var/ossec/integrations/custom-gemini.py
# chown root:wazuh /var/ossec/integrations/custom-gemini.py
  1. Edit the /var/ossec/etc/ossec.conf file and append the integration configuration below:
<ossec_config>
  <integration>
    <name>custom-gemini.py</name>
    <hook_url>https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent</hook_url>
    <api_key><GEMINI_API_KEY></api_key>
    <rule_id>23506,23505,23504,23503</rule_id>
    <alert_format>json</alert_format>
  </integration>
</ossec_config>

Where:

  • <name> specifies the name of the integration script. In this case, custom-gemini.py.
  • <hook_url> specifies the webhook URL of the external application being integrated. 
  • <rule_id> specifies the rule IDs to trigger the Google Gemini integration.
    • Rule ID 23506 corresponds to the out-of-the-box alert generated for critical vulnerabilities.
    • Rule ID 23505 corresponds to the out-of-the-box alert generated for high vulnerabilities.
    • Rule ID 23504 corresponds to the out-of-the-box alert generated for medium vulnerabilities.
    • Rule ID 23503 corresponds to the out-of-the-box alert generated for low vulnerabilities.
  • <api_key> specifies the API key of the reference application. Replace <GEMINI_API_KEY> with the previously generated Gemini API key.
  • <alert_format> specifies that the alerts are received in JSON format.
  1. Create a custom rule file gemini_rules.xml in the  /var/ossec/etc/rules/ directory and insert the following rules:
<group name="vulnerability,gemini,">
  <!-- Enriched alert from Gemini -->
  <rule id="100210" level="13">
    <field name="gemini.source.rule">^23506$</field>
    <field name="gemini.summary">\.+</field>
    <description>[Gemini - $(gemini.source.severity) severity] $(gemini.source.description)</description>
    <options>no_full_log</options>
  </rule>

  <rule id="100211" level="10">
    <field name="gemini.source.rule">^23505$</field>
    <field name="gemini.summary">\.+</field>
    <description>[Gemini - $(gemini.source.severity) severity] $(gemini.source.description)</description>
    <options>no_full_log</options>
  </rule>

  <rule id="100212" level="7">
    <field name="gemini.source.rule">^23504$</field>
    <field name="gemini.summary">\.+</field>
    <description>[Gemini - $(gemini.source.severity) severity] $(gemini.source.description)</description>
    <options>no_full_log</options>
  </rule>

  <rule id="100213" level="3">
    <field name="gemini.source.rule">^23503$</field>
    <field name="gemini.summary">\.+</field>
    <description>[Gemini - $(gemini.source.severity) severity] $(gemini.source.description)</description>
    <options>no_full_log</options>
  </rule>
</group>

Where:

  • Rule ID 100210 is triggered when a critical vulnerability is detected.
  • Rule ID 100211 is triggered when a high vulnerability is detected.
  • Rule ID 100212 is triggered when a medium vulnerability is detected.
  • Rule ID 100213 is triggered when a low vulnerability is detected.
  1. Restart the Wazuh manager to apply the changes:
# systemctl restart wazuh-manager

Testing the integration

We install a vulnerable version of the Chrome browser on the Windows endpoint to test the integration. 

Warning: Install the vulnerable software only in a controlled environment.

The alerts below are generated on the Wazuh dashboard after a scheduled vulnerability scan is completed on the Windows endpoint. Perform the following steps to view the alerts on the Wazuh dashboard.

  1. Navigate to Agents management > Summary and select the Windows agent.
  2. Click on Threat Hunting and select the Events tab.
  3. Click + Add filter. Then filter for rule.groups in the Field field. Select is in the Operator field. 
  4.  Add the filter gemini in the Values field.
  5. Click Save.
Wazuh Dashboard

Click on Inspect document details > View single document to see the alert details. 

Wazuh Dashboard

The data.gemini.summary provides a detailed description of the vulnerability, exploitation prerequisites, patch urgency recommendation, remediation steps, and validation checks. 

Conclusion

By integrating Gemini with the Wazuh vulnerability detection capability, security analysts receive contextual insight, exploitation prerequisites, remediation guidance, and urgency recommendations directly within the Wazuh dashboard. This integration combines Wazuh vulnerability detection capabilities with AI-powered contextual analysis, providing organizations with a structured approach to improving efficiency, accuracy, and response readiness in modern SOC environments.

Discover more about Wazuh by exploring our other blog posts and becoming part of our growing community.

References