git: 88938fcfb8d1 - main - security/wazuh-agent: Add users and groups function support

From: Jose Alonso Cardenas Marquez <acm_at_FreeBSD.org>
Date: Thu, 15 Jan 2026 02:25:41 UTC
The branch main has been updated by acm:

URL: https://cgit.FreeBSD.org/ports/commit/?id=88938fcfb8d169ecc932f3bea0d51c5316e43372

commit 88938fcfb8d169ecc932f3bea0d51c5316e43372
Author:     Jose Alonso Cardenas Marquez <acm@FreeBSD.org>
AuthorDate: 2026-01-15 02:18:58 +0000
Commit:     Jose Alonso Cardenas Marquez <acm@FreeBSD.org>
CommitDate: 2026-01-15 02:18:58 +0000

    security/wazuh-agent: Add users and groups function support
    
    - Now wazuh-agent can obtain users and groups information
    - Fix start_time data to show correct datetime data from wazuh-dashboard processes option
    - Bump PORTREVISION
---
 security/wazuh-agent/Makefile                      |   3 +-
 .../files/patch-src_data__provider-CMakeLists.txt  |  39 +++
 .../files/patch-src_data__provider_CMakeLists.txt  |  11 -
 ...atch-src_data__provider_src-sysInfoFreeBSD.cpp} | 300 +++++++++++++++++----
 ...__provider_src_extended__sources-CMakeLists.txt |  18 ++
 ...der_src_extended__sources_groups-CMakeLists.txt |  22 ++
 ...nded__sources_groups_include-groups_freebsd.hpp |  46 ++++
 ..._sources_groups_include-user_groups_freebsd.hpp |  84 ++++++
 ...extended__sources_groups_src-groups_freebsd.hpp |  95 +++++++
 ...ded__sources_groups_src-user_groups_freebsd.hpp | 262 ++++++++++++++++++
 ...ider_src_extended__sources_users-CMakeLists.txt |  21 ++
 ...urces_users_include-logged_in_users_freebsd.hpp |  37 +++
 ...tended__sources_users_include-users_freebsd.hpp |  69 +++++
 ...__sources_users_src-logged_in_users_freebsd.cpp |  72 +++++
 ...c_extended__sources_users_src-users_freebsd.cpp | 106 ++++++++
 ...ended__sources_wrappers_unix-iutmpx_wrapper.hpp |  12 +
 ...tended__sources_wrappers_unix-utmpx_wrapper.hpp |  16 ++
 ...sources_wrappers_unix_freebsd-group_wrapper.hpp |  93 +++++++
 ...ources_wrappers_unix_freebsd-igroup_wrapper.hpp |  70 +++++
 ...urces_wrappers_unix_freebsd-ipasswd_wrapper.hpp |  72 +++++
 ...ources_wrappers_unix_freebsd-passwd_wrapper.hpp |  96 +++++++
 21 files changed, 1482 insertions(+), 62 deletions(-)

diff --git a/security/wazuh-agent/Makefile b/security/wazuh-agent/Makefile
index 7fce39e00bf9..c5f31572243c 100644
--- a/security/wazuh-agent/Makefile
+++ b/security/wazuh-agent/Makefile
@@ -1,7 +1,7 @@
 PORTNAME=	wazuh
 DISTVERSION=	4.14.1
 DISTVERSIONPREFIX=	v
-PORTREVISION=	5
+PORTREVISION=	6
 CATEGORIES=	security
 MASTER_SITES=	https://packages.wazuh.com/deps/47/libraries/sources/:wazuh_sources
 PKGNAMESUFFIX=	-agent
@@ -120,6 +120,7 @@ post-extract:
 	@cd ${WRKSRC}/src/external && ${EXTRACT_CMD} ${EXTRACT_BEFORE_ARGS} ${_DISTDIR}/${FILE:S/:wazuh_sources//} ${EXTRACT_AFTER_ARGS}
 .endfor
 	@${MKDIR} ${WRKSRC}/ruleset/sca/freebsd
+	@${MKDIR} ${WRKSRC}/src/data_provider/src/extended_sources/wrappers/unix/freebsd
 	@cd ${WRKDIR} && ${EXTRACT_CMD} ${EXTRACT_BEFORE_ARGS} ${_DISTDIR}/${WAZUH_EXTRAFILE} ${EXTRACT_AFTER_ARGS}
 	@${MV} ${WRKDIR}/${PORTNAME}-freebsd-${WAZUH_EXTRAFILE_TAGNAME} ${WRKDIR}/wazuh-freebsd
 	@cd ${WRKDIR}/wazuh-freebsd/var/ossec/ruleset/sca && ${CP} *.yml ${WRKSRC}/ruleset/sca/freebsd/
diff --git a/security/wazuh-agent/files/patch-src_data__provider-CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider-CMakeLists.txt
new file mode 100644
index 000000000000..03dd0efcd223
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider-CMakeLists.txt
@@ -0,0 +1,39 @@
+--- src/data_provider/CMakeLists.txt	2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/CMakeLists.txt	2026-01-13 15:21:46.999172000 +0000
+@@ -104,7 +104,6 @@
+   include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/darwin)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ 
+-
+ if(CMAKE_SYSTEM_NAME STREQUAL "HP-UX")
+   link_directories(${INSTALL_PREFIX}/lib)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "HP-UX")
+@@ -119,6 +118,11 @@
+   include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/darwin)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ 
++if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++  include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/)
++  include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/freebsd)
++endif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++
+ link_directories(${SRC_FOLDER})
+ link_directories(${SRC_FOLDER}/external/sqlite/)
+ link_directories(${SRC_FOLDER}/external/cJSON/)
+@@ -210,6 +214,7 @@
+ 
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
+    CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
++   CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
+    CMAKE_SYSTEM_NAME STREQUAL "Windows")
+   add_subdirectory(src/extended_sources)
+ endif()
+@@ -250,7 +255,7 @@
+   target_link_libraries(sysinfo cjson ${SRC_FOLDER}/external/libplist/bin/lib/libplist-2.0.a ${iokit_lib} ${corefoundation_lib} groups users services browser_extensions)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ 
+-target_link_libraries(sysinfo wazuhext)
++target_link_libraries(sysinfo nghttp2 wazuhext users groups)
+ 
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+   set(CURL_DEP "wazuhext")
diff --git a/security/wazuh-agent/files/patch-src_data__provider_CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider_CMakeLists.txt
deleted file mode 100644
index 0cec8b79cb08..000000000000
--- a/security/wazuh-agent/files/patch-src_data__provider_CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
---- src/data_provider/CMakeLists.txt.orig	2023-05-24 19:23:05 UTC
-+++ src/data_provider/CMakeLists.txt
-@@ -152,7 +152,7 @@ elseif(APPLE)
-   target_link_libraries(sysinfo cjson ${SRC_FOLDER}/external/libplist/bin/lib/libplist-2.0.a)
- endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
- 
--target_link_libraries(sysinfo wazuhext)
-+target_link_libraries(sysinfo nghttp2 wazuhext)
- 
- 
- if(APPLE)
diff --git a/security/wazuh-agent/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp b/security/wazuh-agent/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
similarity index 68%
rename from security/wazuh-agent/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp
rename to security/wazuh-agent/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
index 9fb64aa3c105..8ef1dd376f3e 100644
--- a/security/wazuh-agent/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp
+++ b/security/wazuh-agent/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
@@ -1,6 +1,6 @@
---- src/data_provider/src/sysInfoFreeBSD.cpp.orig	2025-11-07 04:46:03.000000000 -0400
-+++ src/data_provider/src/sysInfoFreeBSD.cpp	2026-01-06 19:37:15.309352000 -0400
-@@ -11,20 +11,28 @@
+--- src/data_provider/src/sysInfoFreeBSD.cpp	2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/sysInfoFreeBSD.cpp	2026-01-14 16:59:37.014537000 +0000
+@@ -11,20 +11,33 @@
  #include "sysInfo.hpp"
  #include "cmdHelper.h"
  #include "stringHelper.h"
@@ -13,6 +13,11 @@
  #include <sys/utsname.h>
  #include "sharedDefs.h"
 +#include <regex>
++#include "groups_freebsd.hpp"
++#include "user_groups_freebsd.hpp"
++#include "logged_in_users_freebsd.hpp"
++#include "sudoers_unix.hpp"
++#include "users_freebsd.hpp"
  
 +const std::string PKG_DB_PATHNAME {"/var/db/pkg/local.sqlite"};
 +const std::string PKG_QUERY {"SELECT p.name, p.maintainer, p.version, p.arch, p.comment, p.flatsize, p.time, v.annotation AS repository,p.origin FROM packages p LEFT JOIN (SELECT pa.package_id, pa.value_id FROM pkg_annotation pa JOIN annotation t ON t.annotation_id = pa.tag_id AND t.annotation = 'repository') pr ON pr.package_id = p.id LEFT JOIN annotation v ON v.annotation_id = pr.value_id;"};
@@ -32,7 +37,7 @@
  
      if (ret)
      {
-@@ -52,11 +60,23 @@
+@@ -52,11 +65,23 @@
          };
      }
  
@@ -59,7 +64,7 @@
  
      if (ret)
      {
-@@ -64,11 +84,11 @@
+@@ -64,11 +89,11 @@
          {
              ret,
              std::system_category(),
@@ -73,7 +78,7 @@
      info["ram_free"] = ramFree;
      info["ram_usage"] = 100 - (100 * ramFree / ramTotal);
  }
-@@ -96,7 +116,43 @@
+@@ -96,7 +121,43 @@
  
  static std::string getSerialNumber()
  {
@@ -118,7 +123,7 @@
  }
  
  static int getCpuCores()
-@@ -184,8 +240,12 @@
+@@ -184,8 +245,12 @@
  
  nlohmann::json SysInfo::getProcessesInfo() const
  {
@@ -133,7 +138,7 @@
  }
  
  nlohmann::json SysInfo::getOsInfo() const
-@@ -196,11 +256,12 @@
+@@ -196,11 +261,12 @@
  
      if (!spParser->parseUname(Utils::exec("uname -r"), ret))
      {
@@ -147,7 +152,7 @@
      if (uname(&uts) >= 0)
      {
          ret["sysname"] = uts.sysname;
-@@ -215,44 +276,257 @@
+@@ -215,44 +281,260 @@
  
  nlohmann::json SysInfo::getPorts() const
  {
@@ -164,23 +169,19 @@
 -void SysInfo::getProcessesInfo(std::function<void(nlohmann::json&)> /*callback*/) const
 -{
 -    // Currently not supported for this OS.
--}
 +    if (!query.empty())
 +    {
 +        nlohmann::json portsjson;
 +        portsjson = nlohmann::json::parse(query);
 +        auto &portsResult = portsjson["sockstat"]["socket"];
- 
--void SysInfo::getPackages(std::function<void(nlohmann::json&)> callback) const
--{
--    const auto query{Utils::exec(R"(pkg query -a "%n|%m|%v|%q|%c")")};
++
 +        for(auto &port : portsResult) {
 +            std::string localip = "";
 +            std::string localport = "";
 +            std::string remoteip = "";
 +            std::string remoteport = "";
 +            std::string statedata = "";
- 
++
 +            if (port["pid"] != nullptr) {
 +
 +                localip = port["local"]["address"];
@@ -228,32 +229,16 @@
 +#else
 +    const auto query{Utils::exec(R"(sockstat -46qs)")};
 +
-     if (!query.empty())
-     {
--        const auto lines{Utils::split(query, '\n')};
++    if (!query.empty())
++    {
 +        const auto lines{Utils::split(Utils::trimToOneSpace(query), '\n')};
- 
++
 +        std::regex expression(R"(^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s*(\S+)\s+(\S+)\s+(\S+)(?:\s+(\S+))?\s*$)");
 +
-         for (const auto& line : lines)
-         {
--            const auto data{Utils::split(line, '|')};
--            nlohmann::json package;
++        for (const auto& line : lines)
++        {
 +            std::smatch data;
- 
--            package["name"] = data[0];
--            package["vendor"] = data[1];
--            package["version"] = data[2];
--            package["install_time"] = UNKNOWN_VALUE;
--            package["location"] = UNKNOWN_VALUE;
--            package["architecture"] = data[3];
--            package["groups"] = UNKNOWN_VALUE;
--            package["description"] = data[4];
--            package["size"] = 0;
--            package["priority"] = UNKNOWN_VALUE;
--            package["source"] = UNKNOWN_VALUE;
--            package["format"] = "pkg";
--            // The multiarch field won't have a default value
++
 +            if (std::regex_search(line, data, expression))
 +            {
 +                std::string localip = "";
@@ -261,8 +246,7 @@
 +                std::string remoteip = "";
 +                std::string remoteport = "";
 +                std::string statedata = "";
- 
--            callback(package);
++
 +                auto localdata{Utils::split(data[6], ':')};
 +                auto remotedata{Utils::split(data[7], ':')};
 +
@@ -315,21 +299,28 @@
 +    }
 +#endif
 +    return ports;
-+}
-+
+ }
+ 
+-void SysInfo::getPackages(std::function<void(nlohmann::json&)> callback) const
 +void SysInfo::getProcessesInfo(std::function<void(nlohmann::json&)> callback) const
-+{
+ {
+-    const auto query{Utils::exec(R"(pkg query -a "%n|%m|%v|%q|%c")")};
 +    const auto query{Utils::exec(R"(ps -ax -w -o pid,comm,state,ppid,usertime,systime,user,ruser,svuid,group,rgroup,svgid,pri,nice,ssiz,vsz,rss,pmem,etimes,sid,pgid,tpgid,tty,cpu,nlwp,args --libxo json)")};
-+
-+    if (!query.empty())
-+    {
+ 
+     if (!query.empty())
+     {
+-        const auto lines{Utils::split(query, '\n')};
 +      nlohmann::json psjson;
++      int64_t agenttime = {Utils::getSecondsFromEpoch()};
++      int64_t etimes{0};
 +      psjson = nlohmann::json::parse(query);
 +      auto &processes = psjson["process-information"]["process"];
-+
+ 
+-        for (const auto& line : lines)
 +      for(auto &process : processes) {
 +          std::string user_time{""};
 +          std::string system_time{""};
++          etimes = std::stoll(process["elapsed-times"].get<std::string>());
 +
 +          user_time = process["user-time"].get<std::string>();
 +          system_time = process["system-time"].get<std::string>();
@@ -356,7 +347,7 @@
 +          jsProcessInfo["vm_size"]    = process["virtual-size"].get<std::string>();
 +          jsProcessInfo["resident"]   = process["rss"].get<std::string>();
 +          //jsProcessInfo["share"]      = process["percent-memory"].get<std::string>();
-+          jsProcessInfo["start_time"] = process["elapsed-times"].get<std::string>() == "-" ? "0" : process["elapsed-times"].get<std::string>();
++          jsProcessInfo["start_time"] = agenttime - etimes;
 +          jsProcessInfo["pgrp"]       = process["process-group"].get<std::string>();
 +          jsProcessInfo["session"]    = process["sid"].get<std::string>();
 +          jsProcessInfo["tgid"]       = process["terminal-process-gid"].get<std::string>();
@@ -374,15 +365,31 @@
 +    if (Utils::existsRegular(PKG_DB_PATHNAME))
 +    {
 +        try
-+        {
+         {
+-            const auto data{Utils::split(line, '|')};
+-            nlohmann::json package;
 +            std::shared_ptr<SQLite::IConnection> sqliteConnection = std::make_shared<SQLite::Connection>(PKG_DB_PATHNAME, SQLITE_OPEN_READONLY);
-+
+ 
+-            package["name"] = data[0];
+-            package["vendor"] = data[1];
+-            package["version"] = data[2];
+-            package["install_time"] = UNKNOWN_VALUE;
+-            package["location"] = UNKNOWN_VALUE;
+-            package["architecture"] = data[3];
+-            package["groups"] = UNKNOWN_VALUE;
+-            package["description"] = data[4];
+-            package["size"] = 0;
+-            package["priority"] = UNKNOWN_VALUE;
+-            package["source"] = UNKNOWN_VALUE;
+-            package["format"] = "pkg";
+-            // The multiarch field won't have a default value
 +            SQLite::Statement stmt
 +            {
 +                sqliteConnection,
 +                PKG_QUERY
 +            };
-+
+ 
+-            callback(package);
 +            while (SQLITE_ROW == stmt.step())
 +            {
 +                try
@@ -432,3 +439,196 @@
      }
  }
  
+@@ -264,14 +546,188 @@
+ 
+ nlohmann::json SysInfo::getGroups() const
+ {
+-    //TODO: Pending implementation.
+-    return nlohmann::json();
++    nlohmann::json result;
++    GroupsProvider groupsProvider;
++    UserGroupsProvider userGroupsProvider;
++
++    auto collectedGroups = groupsProvider.collect({});
++
++    for (auto& group : collectedGroups)
++    {
++        nlohmann::json groupItem {};
++
++        groupItem["group_id"] = group["gid"];
++        groupItem["group_name"] = (group.contains("groupname") && !group["groupname"].get<std::string>().empty()) ? group["groupname"] : UNKNOWN_VALUE;
++        groupItem["group_description"] = UNKNOWN_VALUE;
++        groupItem["group_id_signed"] = group["gid_signed"];
++        groupItem["group_uuid"] = UNKNOWN_VALUE;
++        groupItem["group_is_hidden"] = 0;
++
++        std::set<gid_t> gids {static_cast<gid_t>(group["gid"].get<int>())};
++        auto collectedUsersGroups = userGroupsProvider.getUserNamesByGid(gids);
++
++        if (collectedUsersGroups.empty())
++        {
++            groupItem["group_users"] = UNKNOWN_VALUE;
++        }
++        else
++        {
++            std::string usersConcatenated;
++
++            for (const auto& user : collectedUsersGroups)
++            {
++                if (!usersConcatenated.empty())
++                {
++                    usersConcatenated += secondaryArraySeparator;
++                }
++
++                usersConcatenated += user.get<std::string>();
++            }
++
++            groupItem["group_users"] = usersConcatenated;
++        }
++
++        result.push_back(std::move(groupItem));
++
++    }
++
++    return result;
+ }
+ 
+ nlohmann::json SysInfo::getUsers() const
+ {
+-    //TODO: Pending implementation.
+-    return nlohmann::json();
++    nlohmann::json result;
++
++    UsersProvider usersProvider;
++    auto collectedUsers = usersProvider.collect();
++
++    LoggedInUsersProvider loggedInUserProvider;
++    auto collectedLoggedInUser = loggedInUserProvider.collect();
++
++    UserGroupsProvider userGroupsProvider;
++
++    for (auto& user : collectedUsers)
++    {
++        nlohmann::json userItem {};
++
++        std::string username = (user.contains("username") && !user["username"].get<std::string>().empty()) ? user["username"] : UNKNOWN_VALUE;
++
++        userItem["user_id"] = user["uid"];
++        userItem["user_full_name"] = user["description"];
++        userItem["user_home"] = user["directory"];
++        userItem["user_is_remote"] = user["include_remote"];
++        userItem["user_name"] = username;
++        userItem["user_shell"] = user["shell"];
++        userItem["user_uid_signed"] = user["uid_signed"];
++        userItem["user_group_id_signed"] = user["gid_signed"];
++        userItem["user_group_id"] = user["gid"];
++
++        std::set<uid_t> uid {static_cast<uid_t>(user["uid"].get<int>())};
++        auto collectedUsersGroups = userGroupsProvider.getGroupNamesByUid(uid);
++
++        if (collectedUsersGroups.empty())
++        {
++            userItem["user_groups"] = UNKNOWN_VALUE;
++        }
++        else
++        {
++            std::string accumGroups;
++
++            for (const auto& group : collectedUsersGroups)
++            {
++                if (!accumGroups.empty())
++                {
++                    accumGroups += secondaryArraySeparator;
++                }
++
++                accumGroups += group.get<std::string>();
++            }
++
++            userItem["user_groups"] = accumGroups;
++        }
++
++        // Only in windows
++        userItem["user_type"] = UNKNOWN_VALUE;
++
++        // Macos or windows
++        userItem["user_uuid"] = UNKNOWN_VALUE;
++
++        // Macos
++        userItem["user_is_hidden"] = 0;
++        userItem["user_created"] = 0;
++        userItem["user_auth_failed_count"] = 0;
++        userItem["user_auth_failed_timestamp"] = 0;
++
++        auto matched = false;
++        auto lastLogin = 0;
++
++        userItem["host_ip"] = UNKNOWN_VALUE;
++
++        //TODO: Avoid this iteration, move logic to LoggedInUsersProvider
++        for (auto& item : collectedLoggedInUser)
++        {
++            // By default, user is not logged in.
++            userItem["login_status"] = 0;
++
++            // tty,host,time and pid can take more than one value due to different logins.
++            if (item["user"] == username)
++            {
++                matched = true;
++                userItem["login_status"] = 1;
++
++                auto newDate = item["time"].get<int32_t>();
++
++                if (newDate > lastLogin)
++                {
++                    lastLogin = newDate;
++                    userItem["user_last_login"] = newDate;
++                    userItem["login_tty"] = item["tty"].get<std::string>();
++                    userItem["login_type"] = item["type"].get<std::string>();
++                    userItem["process_pid"] = item["pid"].get<int32_t>();
++                }
++
++                const auto& hostStr = item["host"].get_ref<const std::string&>();
++
++                if (!hostStr.empty())
++                {
++                    userItem["host_ip"] = userItem["host_ip"].get<std::string>() == UNKNOWN_VALUE
++                                          ? hostStr
++                                          : (userItem["host_ip"].get<std::string>() + primaryArraySeparator + hostStr);
++                }
++            }
++        }
++
++        if (!matched)
++        {
++            userItem["login_status"] = 0;
++            userItem["login_tty"] = UNKNOWN_VALUE;
++            userItem["login_type"] = UNKNOWN_VALUE;
++            userItem["process_pid"] = 0;
++            userItem["user_last_login"] = 0;
++        }
++
++        matched = false;
++
++        if (!matched)
++        {
++            userItem["user_password_expiration_date"] = 0;
++            userItem["user_password_hash_algorithm"] = UNKNOWN_VALUE;
++            userItem["user_password_inactive_days"] = 0;
++            userItem["user_password_last_change"] = 0;
++            userItem["user_password_max_days_between_changes"] = 0;
++            userItem["user_password_min_days_between_changes"] = 0;
++            userItem["user_password_status"] = UNKNOWN_VALUE;
++            userItem["user_password_warning_days_before_expiration"] = 0;
++        }
++
++
++        // By default, user is not sudoer.
++        userItem["user_roles"] = UNKNOWN_VALUE;
++
++        result.push_back(std::move(userItem));
++    }
++
++    return result;
+ }
+ 
+ nlohmann::json SysInfo::getServices() const
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt
new file mode 100644
index 000000000000..458b05f1655e
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt
@@ -0,0 +1,18 @@
+--- src/data_provider/src/extended_sources/CMakeLists.txt	2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/extended_sources/CMakeLists.txt	2026-01-13 15:00:46.789677000 +0000
+@@ -1,6 +1,11 @@
+ include_directories(wrappers)
+ 
+-add_subdirectory(groups)
+-add_subdirectory(services)
+-add_subdirectory(users)
+-add_subdirectory(browser_extensions)
++if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++  add_subdirectory(groups)
++  add_subdirectory(users)
++else()
++  add_subdirectory(groups)
++  add_subdirectory(services)
++  add_subdirectory(users)
++  add_subdirectory(browser_extensions)
++endif()
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt
new file mode 100644
index 000000000000..0f34a531e850
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt
@@ -0,0 +1,22 @@
+--- src/data_provider/src/extended_sources/groups/CMakeLists.txt	2026-01-13 15:01:19.871247000 +0000
++++ src/data_provider/src/extended_sources/groups/CMakeLists.txt	2026-01-13 15:07:52.828437000 +0000
+@@ -18,6 +18,8 @@
+     include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/linux)
+   elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+     include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/darwin)
++  elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++    include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/freebsd)
+   endif()
+ endif()
+ 
+@@ -35,6 +37,10 @@
+         list(APPEND SRC_FILES
+           src/groups_linux.cpp
+           src/user_groups_linux.cpp)
++    elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++        list(APPEND SRC_FILES
++          src/groups_freebsd.cpp
++          src/user_groups_freebsd.cpp)
+     elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+         find_library(OPEN_DIRECTORY OpenDirectory)
+         find_library(FOUNDATION Foundation)
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp
new file mode 100644
index 000000000000..a756e0bb02f7
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp
@@ -0,0 +1,46 @@
+--- /dev/null	2026-01-13 23:10:56.926889000 +0000
++++ src/data_provider/src/extended_sources/groups/include/groups_freebsd.hpp	2026-01-13 22:43:51.149789000 +0000
+@@ -0,0 +1,43 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * 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.
++ */
++
++#pragma once
++
++#include <set>
++
++#include "json.hpp"
++#include "igroup_wrapper.hpp"
++
++/// @brief Class for collecting group information on FreeBSD systems.
++///
++/// This class provides methods to collect group information from the
++/// Darwin operating system. It uses the system's group database to retrieve
++/// group details such as group name and GID. The collected data is returned
++/// in JSON format.
++class GroupsProvider
++{
++    public:
++        explicit GroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper);
++
++        /// @brief Default destructor.
++        GroupsProvider();
++
++        /// @brief Collects group information based on provided group IDs.
++        /// @param gids A set of group IDs to collect information for.
++        /// @return A JSON array containing group information.
++        nlohmann::json collect(const std::set<gid_t>& gids = {});
++
++    private:
++        std::shared_ptr<IGroupWrapperFreeBSD> m_groupWrapper;
++
++        /// @brief Adds a group to the results JSON array.
++        /// @param results A reference to the JSON array where the group information will be added.
++        /// @param group A pointer to the group structure containing the group information.
++        void addGroupToResults(nlohmann::json& results, const struct group* group);
++};
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp
new file mode 100644
index 000000000000..3fdc455fed39
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp
@@ -0,0 +1,84 @@
+--- /dev/null	2026-01-13 23:11:31.853795000 +0000
++++ src/data_provider/src/extended_sources/groups/include/user_groups_freebsd.hpp	2026-01-13 22:43:51.149735000 +0000
+@@ -0,0 +1,81 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * 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.
++ */
++
++#pragma once
++
++#include <set>
++
++#include "json.hpp"
++#include "igroup_wrapper.hpp"
++#include "ipasswd_wrapper.hpp"
++
++#define EXPECTED_GROUPS_MAX 64
++
++class UserGroupsProvider
++{
++    public:
++        /// @brief Constructs a UserGroupsProvider with specific wrappers.
++        /// @param groupWrapper A shared pointer to an IGroupWrapperFreeBSD instance for group operations.
++        /// @param passwdWrapper A shared pointer to an IPasswdWrapperFreeBSD instance for passwd operations.
++        /// @param sysWrapper A shared pointer to an ISystemWrapper instance for system operations.
++        explicit UserGroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper,
++                                    std::shared_ptr<IPasswdWrapperFreeBSD> passwdWrapper);
++
++        /// @brief Default constructor that initializes the UserGroupsProvider with default wrappers.
++        /// @note This constructor uses default implementations of IGroupWrapperFreeBSD, and IPasswdWrapperFreeBSD.
++        UserGroupsProvider();
++
++        /// @brief Collects user groups information.
++        /// @param uids A set of user IDs (UIDs) to filter the results. If empty, all users are collected.
++        /// @return A JSON array containing user groups information, where each entry includes UID, GID, and group details.
++        nlohmann::json collect(const std::set<uid_t>& uids = {});
++
++        /// @brief Retrieves group names associated with the specified UIDs.
++        /// @param uids A set of user IDs (UIDs) for which to retrieve group names.
++        /// @return A JSON object where keys are UIDs and values are arrays of group names associated with those UIDs.
++        /// If a UID has no associated groups, the value will be an empty array.
++        /// @note This method is useful for quickly mapping UIDs to their group names without retrieving full group details.
++        /// @note If `uids` is empty, it retrieves group names for all users.
++        nlohmann::json getGroupNamesByUid(const std::set<uid_t>& uids = {});
++
++        /// @brief Retrieves usernames associated with the specified GIDs.
++        /// @param gids A set of group IDs (GIDs) for which to retrieve usernames.
++        /// @return A JSON object where keys are GIDs and values are arrays of usernames associated with those GIDs.
++        /// If a GID has no associated usernames, the value will be an empty array.
++        /// @note This method is useful for quickly mapping GIDs to their usernames without retrieving full user details.
++        /// @note If `gids` is empty, it retrieves usernames for all groups.
++        nlohmann::json getUserNamesByGid(const std::set<gid_t>& gids = {});
++
++    private:
++        std::shared_ptr<IGroupWrapperFreeBSD> m_groupWrapper;
++        std::shared_ptr<IPasswdWrapperFreeBSD> m_passwdWrapper;
++
++        /// @brief Structure to hold user information.
++        struct UserInfo
++        {
++            const char* name;
++            uid_t uid;
++            gid_t gid;
++        };
++
++        /// @brief Retrieves groups for each user and returns a vector of pairs containing UID and their associated groups.
++        /// @param uids A set of user IDs (UIDs) to filter the results. If empty, all users are processed.
++        /// @return A vector of pairs, where each pair contains a UID and a vector of GIDs representing the groups associated with that UID.
++        /// @note This method is used internally to gather user-group associations before formatting the results into JSON.
++        /// @note If a user has no associated groups, the vector of GIDs will be empty.
++        std::vector<std::pair<uid_t, std::vector<gid_t>>> getUserGroups(const std::set<uid_t>& uids);
++
++        /// @brief Adds groups to the results JSON array for a specific user.
++        /// @param results A reference to the JSON array where the group information will be added.
++        /// @param uid The user ID for which the groups are being added.
++        /// @param groups A pointer to an array of group IDs (GIDs) associated with the user.
++        /// @param ngroups The number of groups in the `groups` array.
++        /// @note This method formats the group information into JSON objects and appends them to the results array.
++        void addGroupsToResults(nlohmann::json& results, uid_t uid, const gid_t* groups, int ngroups);
++};
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp
new file mode 100644
index 000000000000..d65c4b178507
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp
@@ -0,0 +1,95 @@
+--- /dev/null	2026-01-13 23:12:14.070467000 +0000
++++ src/data_provider/src/extended_sources/groups/src/groups_freebsd.cpp	2026-01-13 22:43:51.150488000 +0000
+@@ -0,0 +1,92 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * 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.
++ */
++
++#include "groups_freebsd.hpp"
++#include "group_wrapper.hpp"
++
++constexpr size_t MAX_GETPW_R_BUF_SIZE = 16 * 1024;
++
++GroupsProvider::GroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper)
++    : m_groupWrapper(std::move(groupWrapper)) {}
++
++GroupsProvider::GroupsProvider()
++    : m_groupWrapper(std::make_shared<GroupWrapperFreeBSD>()) {}
++
++nlohmann::json GroupsProvider::collect(const std::set<gid_t>& gids)
++{
++    nlohmann::json results = nlohmann::json::array();
++    struct group* groupResult
++    {
++        nullptr
++    };
++    struct group group;
++
++    size_t bufSize = MAX_GETPW_R_BUF_SIZE;
++    auto buf = std::make_unique<char[]>(bufSize);
++
++    if (!gids.empty())
++    {
++        for (const auto& gid : gids)
++        {
++            while (m_groupWrapper->getgrgid_r(gid, &group, buf.get(), bufSize, &groupResult) == ERANGE)
++            {
++                bufSize *= 2;
++                buf = std::make_unique<char[]>(bufSize);
++            }
++
++            if (groupResult == nullptr)
++            {
++                continue;
++            }
++
++            addGroupToResults(results, groupResult);
++        }
++    }
++    else
++    {
++        std::set<long> groupsIn;
++        m_groupWrapper->setgrent();
++
++        while (1)
++        {
++            while (m_groupWrapper->getgrent_r(&group, buf.get(), bufSize, &groupResult) == ERANGE)
++            {
++                bufSize *= 2;
++                buf = std::make_unique<char[]>(bufSize);
++            }
++
++            if (groupResult == nullptr)
++            {
++                break;
++            }
++
++            if (std::find(groupsIn.begin(), groupsIn.end(), groupResult->gr_gid) == groupsIn.end())
++            {
++                addGroupToResults(results, groupResult);
++                groupsIn.insert(groupResult->gr_gid);
++            }
++        }
++
++        m_groupWrapper->endgrent();
++        groupsIn.clear();
++    }
++
++    return results;
++}
++
++void GroupsProvider::addGroupToResults(nlohmann::json& results, const group* group)
++{
++    nlohmann::json groupJson;
++
++    groupJson["groupname"] = group->gr_name;
++    groupJson["gid"] = group->gr_gid;
++    groupJson["gid_signed"] = static_cast<int32_t>(group->gr_gid);
++
++    results.push_back(groupJson);
++}
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp
new file mode 100644
index 000000000000..ef6229d3ef82
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp
@@ -0,0 +1,262 @@
+--- /dev/null	2026-01-13 23:12:49.062343000 +0000
++++ src/data_provider/src/extended_sources/groups/src/user_groups_freebsd.cpp	2026-01-13 22:43:51.150552000 +0000
+@@ -0,0 +1,259 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * 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.
++ */
++
++#include <iostream>
++#include <unistd.h>
++#include "user_groups_freebsd.hpp"
++#include "group_wrapper.hpp"
++#include "passwd_wrapper.hpp"
++
++constexpr size_t MAX_GETPW_R_BUF_SIZE = 16 * 1024;
++
++UserGroupsProvider::UserGroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper,
++                                       std::shared_ptr<IPasswdWrapperFreeBSD> passwdWrapper)
++    : m_groupWrapper(std::move(groupWrapper))
++    , m_passwdWrapper(std::move(passwdWrapper))
++{
++}
++
++UserGroupsProvider::UserGroupsProvider()
++    : m_groupWrapper(std::make_shared<GroupWrapperFreeBSD>())
++    , m_passwdWrapper(std::make_shared<PasswdWrapperFreeBSD>())
++{
++}
++
++nlohmann::json UserGroupsProvider::collect(const std::set<uid_t>& uids)
++{
++    nlohmann::json results = nlohmann::json::array();
++    auto usersGroups = getUserGroups(uids);
++
++    for (const auto& [uid, groups] : usersGroups)
++    {
++        addGroupsToResults(results, uid, groups.data(), static_cast<int>(groups.size()));
++    }
++
++    return results;
++}
++
++nlohmann::json UserGroupsProvider::getGroupNamesByUid(const std::set<uid_t>& uids)
++{
++    const bool singleUid = (uids.size() == 1);
++    nlohmann::json result = singleUid ? nlohmann::json::array() : nlohmann::json::object();
++    auto usersGroups = getUserGroups(uids);
++
++    size_t bufSize = sysconf(_SC_GETGR_R_SIZE_MAX);
++
++    if (bufSize > MAX_GETPW_R_BUF_SIZE)
++    {
++        bufSize = MAX_GETPW_R_BUF_SIZE;
++    }
++
++    for (const auto& [uid, groups] : usersGroups)
++    {
++        nlohmann::json groupNames = nlohmann::json::array();
++
++        for (const auto& gid : groups)
++        {
++            struct group grp;
++            struct group* grpResult = nullptr;
++            auto groupBuf = std::make_unique<char[]>(bufSize);
++
++            if (m_groupWrapper->getgrgid_r(gid, &grp, groupBuf.get(), bufSize, &grpResult) == 0 && grpResult != nullptr)
++            {
++                groupNames.push_back(grpResult->gr_name);
++            }
++        }
++
++        if (singleUid)
++        {
++            result = groupNames;
++        }
++        else
++        {
++            result[std::to_string(uid)] = groupNames;
++        }
++    }
++
++    return result;
++}
++
++nlohmann::json UserGroupsProvider::getUserNamesByGid(const std::set<gid_t>& gids)
++{
++    const bool allGroups = gids.empty();
++    const bool singleGid = (!allGroups && gids.size() == 1);
++    nlohmann::json result = singleGid ? nlohmann::json::array() : nlohmann::json::object();
++
++    size_t bufSize = sysconf(_SC_GETPW_R_SIZE_MAX);
++
++    if (bufSize > MAX_GETPW_R_BUF_SIZE)
++    {
++        bufSize = MAX_GETPW_R_BUF_SIZE;
++    }
++
++    std::map<gid_t, std::set<std::string>> gidToUsernames;
++
++    if (allGroups)
++    {
++        struct group* grp = nullptr;
++        m_groupWrapper->setgrent();
++
++        while ((grp = m_groupWrapper->getgrent()) != nullptr)
++        {
++            gid_t gid = grp->gr_gid;
++            char** members = grp->gr_mem;
++
++            while (members && *members)
++            {
++                gidToUsernames[gid].insert(*members);
++                ++members;
++            }
++        }
++
++        m_groupWrapper->endgrent();
++    }
++    else
++    {
++        for (const auto& gid : gids)
++        {
++            struct group grp;
++            struct group* grpResult = nullptr;
++            auto groupBuf = std::make_unique<char[]>(bufSize);
*** 863 LINES SKIPPED ***