You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS EndpointSecurity客户端指定路径监听问题排查与实现

macOS EndpointSecurity 客户端问题排查与优化

一、无法监听目标路径的核心问题

你的代码存在关键逻辑错误:误将触发事件的进程可执行文件路径,当作被操作的文件路径进行判断,导致永远匹配不到目标目录的事件。

问题代码分析

在handleEvent函数中,你获取的msg->process->executable->path.data是触发事件的进程路径(比如打开文件的TextEdit路径为/Applications/TextEdit.app/Contents/MacOS/TextEdit),而非被操作的文件路径。因此[filePathString hasPrefix:path]的判断永远不成立,自然不会输出目标路径的事件日志。

解决方法

针对不同事件类型,获取对应的目标文件路径,示例对应关系:

  • ES_EVENT_TYPE_NOTIFY_OPEN → msg->event.open.target->path.data
  • ES_EVENT_TYPE_NOTIFY_CREATE → msg->event.create.target->path.data
  • ES_EVENT_TYPE_NOTIFY_WRITE → msg->event.write.target->path.data
  • 其他事件类型需参照EndpointSecurity框架文档中es_message_t的结构定义获取对应路径。

二、仅保留目标路径监听(避免事件队列溢出)

EndpointSecurity默认推送所有路径的事件,若要仅监听指定路径,正确策略是:

  1. 先静音根目录/,屏蔽所有路径的事件
  2. 再解除目标路径的静音,仅保留该路径的事件推送

这种方式能大幅减少事件数量,从根源避免队列溢出导致的事件丢失。

三、完整修正后的代码

#include <EndpointSecurity/EndpointSecurity.h>
#include <os/log.h>
#include <dispatch/dispatch.h>
#include <Foundation/Foundation.h>

static void handleEvent(es_client_t *client, const es_message_t *msg) {
    const char *targetPath = NULL;
    
    // 根据事件类型获取对应的目标文件路径
    switch (msg->event_type) {
        case ES_EVENT_TYPE_NOTIFY_CREATE:
            targetPath = msg->event.create.target->path.data;
            break;
        case ES_EVENT_TYPE_NOTIFY_OPEN:
            targetPath = msg->event.open.target->path.data;
            break;
        case ES_EVENT_TYPE_NOTIFY_RENAME:
            targetPath = msg->event.rename.target->path.data;
            break;
        case ES_EVENT_TYPE_NOTIFY_CLOSE:
            targetPath = msg->event.close.target->path.data;
            break;
        case ES_EVENT_TYPE_NOTIFY_WRITE:
            targetPath = msg->event.write.target->path.data;
            break;
        case ES_EVENT_TYPE_NOTIFY_UNLINK:
            targetPath = msg->event.unlink.target->path.data;
            break;
        case ES_EVENT_TYPE_NOTIFY_EXIT:
            // EXIT事件无目标文件路径,直接处理
            break;
        default:
            os_log_error(OS_LOG_DEFAULT, "未知事件类型: %d", msg->event_type);
            return;
    }

    // 单独处理EXIT事件
    if (msg->event_type == ES_EVENT_TYPE_NOTIFY_EXIT) {
        os_log(OS_LOG_DEFAULT, "%{public}s (进程ID: %d) | EXIT: 退出状态: %d",
               msg->process->executable->path.data,
               audit_token_to_pid(msg->process->audit_token),
               msg->event.exit.stat);
        return;
    }

    // 目标路径为空时直接返回
    if (!targetPath) {
        return;
    }

    NSString *targetPathString = [NSString stringWithUTF8String:targetPath];
    NSString *watchPath = @"/Users/anoopvaidya/Documents";

    os_log(OS_LOG_DEFAULT, "目标路径: %@, 监听路径: %@", targetPathString, watchPath);

    // 判断是否为目标路径下的事件
    if ([targetPathString hasPrefix:watchPath]) {
        os_log(OS_LOG_DEFAULT, "匹配到目标路径: %@", targetPathString);
        
        // 输出对应事件详情
        switch (msg->event_type) {
            case ES_EVENT_TYPE_NOTIFY_CREATE:
                os_log(OS_LOG_DEFAULT, "CREATE: %{public}s", targetPath);
                break;
            case ES_EVENT_TYPE_NOTIFY_OPEN:
                os_log(OS_LOG_DEFAULT, "OPEN: %{public}s", targetPath);
                break;
            case ES_EVENT_TYPE_NOTIFY_RENAME:
                os_log(OS_LOG_DEFAULT, "RENAME: %{public}s -> %{public}s",
                       targetPath, msg->event.rename.new_path->path.data);
                break;
            case ES_EVENT_TYPE_NOTIFY_CLOSE:
                os_log(OS_LOG_DEFAULT, "CLOSE: %{public}s", targetPath);
                break;
            case ES_EVENT_TYPE_NOTIFY_WRITE:
                os_log(OS_LOG_DEFAULT, "WRITE: %{public}s", targetPath);
                break;
            case ES_EVENT_TYPE_NOTIFY_UNLINK:
                os_log(OS_LOG_DEFAULT, "UNLINK: %{public}s", targetPath);
                break;
            default:
                break;
        }
    }
}

void setupPathFilter(es_client_t *client) {
    // 1. 先静音根目录,屏蔽所有路径
    if (es_mute_path_prefix(client, "/") != ES_RETURN_SUCCESS) {
        os_log_error(OS_LOG_DEFAULT, "静音根目录失败");
    }
    
    // 2. 解除目标路径的静音,仅监听该路径
    NSString *watchPath = @"/Users/anoopvaidya/Documents";
    if (es_unmute_path_prefix(client, [watchPath UTF8String]) != ES_RETURN_SUCCESS) {
        os_log_error(OS_LOG_DEFAULT, "解除目标路径静音失败");
    }
}

int main(int argc, char *argv[]) {
    es_client_t *client = NULL;
    es_new_client_result_t newClientResult = es_new_client(&client, ^(es_client_t *c, const es_message_t *message) {
        handleEvent(client, message);
    });

    if (newClientResult != ES_NEW_CLIENT_RESULT_SUCCESS) {
        os_log_error(OS_LOG_DEFAULT, "创建ES客户端失败: %d", newClientResult);
        return 1;
    }
    
    // 设置路径过滤规则
    setupPathFilter(client);
    
    // 订阅需要的事件类型
    es_event_type_t events[] = {
        ES_EVENT_TYPE_NOTIFY_CREATE,
        ES_EVENT_TYPE_NOTIFY_OPEN,
        ES_EVENT_TYPE_NOTIFY_RENAME,
        ES_EVENT_TYPE_NOTIFY_CLOSE,
        ES_EVENT_TYPE_NOTIFY_WRITE,
        ES_EVENT_TYPE_NOTIFY_UNLINK,
        ES_EVENT_TYPE_NOTIFY_EXIT
    };

    if (es_subscribe(client, events, sizeof(events) / sizeof(events[0])) != ES_RETURN_SUCCESS) {
        os_log_error(OS_LOG_DEFAULT, "订阅事件失败");
        es_delete_client(client);
        return 1;
    }

    dispatch_main();
}

额外注意事项

  • 必须以root权限运行客户端,否则无法获取部分用户目录的事件
  • 测试时可先简化逻辑,比如去掉路径过滤,确认能收到事件后再逐步添加规则
  • EndpointSecurity对高频率事件可能进行合并或丢弃,精准的路径过滤是避免该问题的最优方案

内容的提问来源于stack exchange,提问作者Anoop Vaidya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 04:10:00