Unable to enable integration for: 'virustotal'. File not found inside 'integrations'
03:43 02 Dec 2025

I was integrating Wazuh with virus total, at first it was working fine but lately they're not sending anymore. I'm looking for the issue and i found something like this on the log.

Dec 2, 2025 @ 22:30:52.000 wazuh-integratord INFO (1225): SIGNAL \[(15)-(Terminated)\] Received. Exit Cleaning...
Dec 2, 2025 @ 22:30:58.000 wazuh-integratord INFO Started (pid: 1029955).
Dec 2, 2025 @ 22:30:58.000 wazuh-integratord ERROR Unable to enable integration for: 'virustotal'. File not found inside 'integrations'.

And i found it's inside the integrations

root@ubuntu-wazuh:/var/ossec/etc/lists# ls -l /var/ossec/integrations | grep virustotal
-rwxr-x--- 1 root wazuh 1045 Dec 2 10:00 virustotal
-rwxr-x--- 1 root wazuh 11082 Dec 1 17:18 virustotal.py

Integration on ossec.conf


virustotal https://www.virustotal.com/vtapi/v2/file/report API
syscheck
json

root@ubuntu-wazuh:/var/ossec/integrations# cat virustotal
#!/bin/sh
# Copyright (C) 2015, Wazuh Inc.
# Created by Wazuh, Inc. .
# This program is free software; you can redistribute it and/or modify it under the terms of GPLv2

WPYTHON_BIN="framework/python/bin/python3"

SCRIPT_PATH_NAME="$0"

DIR_NAME="$(cd $(dirname ${SCRIPT_PATH_NAME}); pwd -P)"
SCRIPT_NAME="$(basename ${SCRIPT_PATH_NAME})"

case ${DIR_NAME} in
    */active-response/bin | */wodles*)
        if [ -z "${WAZUH_PATH}" ]; then
            WAZUH_PATH="$(cd ${DIR_NAME}/../..; pwd)"
        fi

        PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py"
    ;;
    */bin)
        if [ -z "${WAZUH_PATH}" ]; then
            WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)"
        fi

        PYTHON_SCRIPT="${WAZUH_PATH}/framework/scripts/$(echo ${SCRIPT_NAME} | sed 's/\-/_/g').py"
    ;;
     */integrations)
        if [ -z "${WAZUH_PATH}" ]; then
            WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)"
        fi

        PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py"
    ;;
esac


${WAZUH_PATH}/${WPYTHON_BIN} ${PYTHON_SCRIPT} "$@"



root@ubuntu-wazuh:/var/ossec/integrations# cat virustotal.py
# Copyright (C) 2015, Wazuh Inc.
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License (version 2) as published by the FSF - Free Software
# Foundation.


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

# Exit error codes
ERR_NO_REQUEST_MODULE = 1
ERR_BAD_ARGUMENTS = 2
ERR_BAD_MD5_SUM = 3
ERR_NO_RESPONSE_VT = 4
ERR_SOCKET_OPERATION = 5
ERR_FILE_NOT_FOUND = 6
ERR_INVALID_JSON = 7

try:
    import requests
    from requests.exceptions import Timeout
except Exception:
    print("No module 'requests' found. Install: pip install requests")
    sys.exit(ERR_NO_REQUEST_MODULE)

# ossec.conf configuration:
# 
#   virustotal
#   API_KEY 
#   syscheck
#   json
# 

# Global vars
debug_enabled = False
timeout = 10
retries = 3
pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
json_alert = {}

# Log and socket path
LOG_FILE = f'{pwd}/logs/integrations.log'
SOCKET_ADDR = f'{pwd}/queue/sockets/queue'

# Constants
ALERT_INDEX = 1
APIKEY_INDEX = 2
TIMEOUT_INDEX = 6
RETRIES_INDEX = 7


def main(args):
    global debug_enabled
    global timeout
    global retries
    try:
        # Read arguments
        bad_arguments: bool = False
        if len(args) >= 4:
            msg = '{0} {1} {2} {3} {4} {5} {6}'.format(
                args[1],
                args[2],
                args[3],
                args[4] if len(args) > 4 else '',
                args[5] if len(args) > 5 else '',
                args[TIMEOUT_INDEX] if len(args) > TIMEOUT_INDEX else timeout,
                args[RETRIES_INDEX] if len(args) > RETRIES_INDEX else retries,
            )
            debug_enabled = len(args) > 4 and args[4] == 'debug'
            if len(args) > TIMEOUT_INDEX:
                timeout = int(args[TIMEOUT_INDEX])
            if len(args) > RETRIES_INDEX:
                retries = int(args[RETRIES_INDEX])
        else:
            msg = '# Error: Wrong arguments'
            bad_arguments = True

        # Logging the call
        with open(LOG_FILE, 'a') as f:
            f.write(msg + '\n')

        if bad_arguments:
            debug('# Error: Exiting, bad arguments. Inputted: %s' % args)
            sys.exit(ERR_BAD_ARGUMENTS)

        # Core function
        process_args(args)

    except Exception as e:
        debug(str(e))
        raise


def process_args(args) -> None:
    """This is the core function, creates a message with all valid fields
    and overwrite or add with the optional fields

    Parameters
    ----------
    args : list[str]
        The argument list from main call
    """
    debug('# Running VirusTotal script')

    # Read args
    alert_file_location: str = args[ALERT_INDEX]
    apikey: str = args[APIKEY_INDEX]

    # Load alert. Parse JSON object.
    json_alert = get_json_alert(alert_file_location)
    debug(f"# Opening alert file at '{alert_file_location}' with '{json_alert}'")

    # Request VirusTotal info
    debug('# Requesting VirusTotal information')
    msg: any = request_virustotal_info(json_alert, apikey)

    if not msg:
        debug('# Error: Empty message')
        raise Exception

    send_msg(msg, json_alert['agent'])


def debug(msg: str) -> None:
    """Log the message in the log file with the timestamp, if debug flag
    is enabled

    Parameters
    ----------
    msg : str
        The message to be logged.
    """
    if debug_enabled:
        print(msg)
        with open(LOG_FILE, 'a') as f:
            f.write(msg + '\n')


def request_info_from_api(alert, alert_output, api_key):
    """Request information from an API using the provided alert and API key.

    Parameters
    ----------
    alert : dict
        The alert dictionary containing information for the API request.
    alert_output : dict
        The output dictionary where API response information will be stored.
    api_key : str
        The API key required for making the API request.

    Returns
    -------
    dict
        The response data received from the API.

    Raises
    ------
    Timeout
        If the API request times out.
    Exception
        If an unexpected exception occurs during the API request.
    """
    for attempt in range(retries + 1):
        try:
            vt_response_data = query_api(alert['syscheck']['md5_after'], api_key)
            return vt_response_data
        except Timeout:
            debug('# Error: Request timed out. Remaining retries: %s' % (retries - attempt))
            continue
        except Exception as e:
            debug(str(e))
            sys.exit(ERR_NO_RESPONSE_VT)

    debug('# Error: Request timed out and maximum number of retries was exceeded')
    alert_output['virustotal']['error'] = 408
    alert_output['virustotal']['description'] = 'Error: API request timed out'
    send_msg(alert_output)
    sys.exit(ERR_NO_RESPONSE_VT)


def request_virustotal_info(alert: any, apikey: str):
    """Generate the JSON object with the message to be send

    Parameters
    ----------
    alert : any
        JSON alert object.
    apikey : str
        The API key required for making the API request.

    Returns
    -------
    msg: str
        The JSON message to send
    """
    alert_output = {'virustotal': {}, 'integration': 'virustotal'}

    # If there is no syscheck block present in the alert. Exit.
    if 'syscheck' not in alert:
        debug('# No syscheck block present in the alert')
        return None

    # If there is no md5 checksum present in the alert. Exit.
    if 'md5_after' not in alert['syscheck']:
        debug('# No md5 checksum present in the alert')
        return None

    # If the md5_after field is not a md5 hash checksum. Exit
    if not (
        isinstance(alert['syscheck']['md5_after'], str) is True
        and len(re.findall(r'\b([a-f\d]{32}|[A-F\d]{32})\b', alert['syscheck']['md5_after'])) == 1
    ):
        debug('# md5_after field in the alert is not a md5 hash checksum')
        return None

    # Request info using VirusTotal API
    vt_response_data = request_info_from_api(alert, alert_output, apikey)

    alert_output['virustotal']['found'] = 0
    alert_output['virustotal']['malicious'] = 0
    alert_output['virustotal']['source'] = {
        'alert_id': alert['id'],
        'file': alert['syscheck']['path'],
        'md5': alert['syscheck']['md5_after'],
        'sha1': alert['syscheck']['sha1_after'],
    }

    # Check if VirusTotal has any info about the hash
    if in_database(vt_response_data, hash):
        alert_output['virustotal']['found'] = 1

    # Info about the file found in VirusTotal
    if alert_output['virustotal']['found'] == 1:
        if vt_response_data['positives'] > 0:
            alert_output['virustotal']['malicious'] = 1

        # Populate JSON Output object with VirusTotal request
        alert_output['virustotal'].update(
            {
                'sha1': vt_response_data['sha1'],
                'scan_date': vt_response_data['scan_date'],
                'positives': vt_response_data['positives'],
                'total': vt_response_data['total'],
                'permalink': vt_response_data['permalink'],
            }
        )

    return alert_output


def in_database(data, hash):
    result = data['response_code']
    if result == 0:
        return False
    return True


def query_api(hash: str, apikey: str) -> any:
    """Send a request to VT API and fetch information to build message

    Parameters
    ----------
    hash : str
        Hash need it for parameters
    apikey: str
        Authentication API

    Returns
    -------
    data: any
        JSON with the response

    Raises
    ------
    Exception
        If the status code is different than 200.
    """
    params = {'apikey': apikey, 'resource': hash}
    headers = {'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'gzip,  Python library-client-VirusTotal'}

    debug('# Querying VirusTotal API')
    response = requests.get(
        'https://www.virustotal.com/vtapi/v2/file/report', params=params, headers=headers, timeout=timeout
    )

    if response.status_code == 200:
        json_response = response.json()
        vt_response_data = json_response
        return vt_response_data
    else:
        alert_output = {}
        alert_output['virustotal'] = {}
        alert_output['integration'] = 'virustotal'

        if response.status_code == 204:
            alert_output['virustotal']['error'] = response.status_code
            alert_output['virustotal']['description'] = 'Error: Public API request rate limit reached'
            send_msg(alert_output)
            raise Exception('# Error: VirusTotal Public API request rate limit reached')
        elif response.status_code == 403:
            alert_output['virustotal']['error'] = response.status_code
            alert_output['virustotal']['description'] = 'Error: Check credentials'
            send_msg(alert_output)
            raise Exception('# Error: VirusTotal credentials, required privileges error')
        else:
            alert_output['virustotal']['error'] = response.status_code
            alert_output['virustotal']['description'] = 'Error: API request fail'
            send_msg(alert_output)
            raise Exception('# Error: VirusTotal credentials, required privileges error')


def send_msg(msg: any, agent: any = None) -> None:
    if not agent or agent['id'] == '000':
        string = '1:virustotal:{0}'.format(json.dumps(msg))
    else:
        location = '[{0}] ({1}) {2}'.format(agent['id'], agent['name'], agent['ip'] if 'ip' in agent else 'any')
        location = location.replace('|', '||').replace(':', '|:')
        string = '1:{0}->virustotal:{1}'.format(location, json.dumps(msg))

    debug('# Request result from VT server: %s' % string)
    try:
        sock = socket(AF_UNIX, SOCK_DGRAM)
        sock.connect(SOCKET_ADDR)
        sock.send(string.encode())
        sock.close()
    except FileNotFoundError:
        debug('# Error: Unable to open socket connection at %s' % SOCKET_ADDR)
        sys.exit(ERR_SOCKET_OPERATION)


def get_json_alert(file_location: str) -> any:
    """Read JSON alert object from file

    Parameters
    ----------
    file_location : str
        Path to the JSON file location.

    Returns
    -------
    dict: any
        The JSON object read it.

    Raises
    ------
    FileNotFoundError
        If no JSON file is found.
    JSONDecodeError
        If no valid JSON file are used
    """
    try:
        with open(file_location) as alert_file:
            return json.load(alert_file)
    except FileNotFoundError:
        debug("# JSON file for alert %s doesn't exist" % file_location)
        sys.exit(ERR_FILE_NOT_FOUND)
    except json.decoder.JSONDecodeError as e:
        debug('Failed getting JSON alert. Error: %s' % e)
        sys.exit(ERR_INVALID_JSON)


if __name__ == '__main__':
    main(sys.argv)

Any ideas?

wazuh antivirus-integration