{
	"id": "f155bd4d-3f54-4964-9844-e76399c126e8",
	"created_at": "2026-04-06T00:14:18.883527Z",
	"updated_at": "2026-04-10T03:20:44.658901Z",
	"deleted_at": null,
	"sha1_hash": "1b0dfd07316e8f8db1ea2db5aa2be2930a3a7e9c",
	"title": "Writing a File Monitor with Apple's Endpoint Security Framework",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 537622,
	"plain_text": "Writing a File Monitor with Apple's Endpoint Security Framework\r\nArchived: 2026-04-05 20:10:41 UTC\r\nWriting a File Monitor with Apple's Endpoint Security Framework\r\nby: Patrick Wardle / September 17, 2019\r\nOur research, tools, and writing, are supported by \"Friends of Objective-See\"\r\nToday’s blog post is brought to you by:\r\n CleanMyMac\r\n# ./fileMonitor\r\nStarting file monitor...[ok]\r\nFILE CREATE ('ES_EVENT_TYPE_NOTIFY_CREATE')\r\nsource path: (null)\r\ndestination path: /private/tmp/test\r\nprocess: pid: 849\r\npath: /usr/bin/touch\r\nuid: 501\r\nsigning info: {\r\n cdHash = 818C29925EE42814EFA951413B713788AD62;\r\n csFlags = 603996161;\r\n isPlatforBinary = 1;\r\n signatureIdentifier = \"com.apple.touch\";\r\n}\r\nBackground\r\nEarlier this week, I posted a blog titled “Writing a Process Monitor with Apple’s Endpoint Security Framework.”\r\nIn this post we (rather thoroughly) discussed a new framework/subsystem introduced into macOS Catalina\r\n(10.15): “Endpoint Security”\r\nMoreover, we detailed exactly how to build a comprehensive (user-mode) process monitor that leveraged this new\r\nframework (and posted the full-source online).\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 1 of 15\n\nThis blog post assumes you’ve read the previous post, or have a solid understanding of the new Endpoint Security\r\nframework.\r\nAs such, here, we won’t be covering any foundational details about the Endpoint Security framework/subsystem.\r\nA common component of (many) security tools is a file monitor. As its name implies, a file monitor watches for\r\nthe file I/O events (plus generally extracts information about the process responsible for said file event).\r\nMany of my Objective-See tools contain such a file monitor component and track file events.\r\nExamples include:\r\nRansomwhere?\r\nTracks file creations to detect the rapid creation of encrypted files by untrusted processes (read:\r\nransomware).\r\nBlockBlock\r\nTracks file creations and modifications in order to detect and alert on persistence events (such as malware\r\ninstallation).\r\nUntil now, the preferred way to programmatically create a file monitor in user-mode was to subscribe to events\r\nfrom the FSEvents character device ( /dev/fsevents ):\r\n1open(\"/dev/fsevents\", O_RDONLY);\r\nThough directly reading file events off /dev/fsevents , is sufficient (that is to say it provides notifications about\r\nfile events, and includes the pid of the responsible process) it suffers from various drawbacks and limitations.\r\nFirst, Apple actually discourages it use (as noted in the bsd/vfs/vfs_fsevents.c file):\r\n 1if (!strncmp(watcher-\u003eproc_name, \"fseventsd\", sizeof(watcher-\u003eproc_name)) ||\r\n 2 !strncmp(watcher-\u003eproc_name, \"coreservicesd\", sizeof(watcher-\u003eproc_name)) ||\r\n 3 !strncmp(watcher-\u003eproc_name, \"mds\", sizeof(watcher-\u003eproc_name))) {\r\n 4\r\n 5 watcher-\u003eflags |= WATCHER_APPLE_SYSTEM_SERVICE;\r\n 6\r\n 7} else {\r\n 8\r\n 9 printf(\"fsevents: watcher %s (pid: %d) -\r\n10 Using /dev/fsevents directly is unsupported. Migrate to FSEventsFramework\\n\",\r\n11 watcher-\u003eproc_name, watcher-\u003epid);\r\n12}\r\nSecond, it is rather painful to programmatically interface with, as it it requires one to parse and tokenize various\r\n(binary) file events:\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 2 of 15\n\n1//skip over args to get to next event struct\r\n 2-(NSString*)advance2Next:(unsigned char*)ptrBuffer currentOffsetPtr:(int*)ptrCurrentOffset\r\n 3{\r\n 4 //path\r\n 5 NSString* path = nil;\r\n 6\r\n 7 int arg_len = 0;\r\n 8 unsigned short *argLen;\r\n 9 unsigned short *argType;\r\n10 struct kfs_event_a *fse;\r\n11 struct kfs_event_arg *fse_arg;\r\n12\r\n13 fse = (struct kfs_event_a *)(unsigned char*)\r\n14 ((unsigned char*)ptrBuffer + *ptrCurrentOffset);\r\n15\r\n16 //handle dropped events\r\n17 if(fse-\u003etype == FSE_EVENTS_DROPPED)\r\n18 {\r\n19 //err msg\r\n20 logMsg(LOG_ERR, @\"file-system events dropped by kernel\");\r\n21\r\n22 //advance to next\r\n23 *ptrCurrentOffset += sizeof(kfs_event_a) + sizeof(fse-\u003etype);\r\n24\r\n25 //exit early\r\n26 return nil;\r\n27 }\r\n28\r\n29 *ptrCurrentOffset += sizeof(struct kfs_event_a);\r\n30 fse_arg = (struct kfs_event_arg *)\u0026ptrBuffer[*ptrCurrentOffset];\r\n31\r\n32 //save path\r\n33 path = [NSString stringWithUTF8String:fse_arg-\u003edata];\r\n34\r\n35 //skip over path\r\n36 *ptrCurrentOffset += sizeof(kfs_event_arg) + fse_arg-\u003epathlen ;\r\n37\r\n38 argType = (unsigned short *)(unsigned char*)\r\n39 ((unsigned char*)ptrBuffer + *ptrCurrentOffset);\r\n40 argLen = (unsigned short *) (ptrBuffer + *ptrCurrentOffset + 2);\r\n41\r\n42 (*argType == FSE_ARG_DONE) ? arg_len = 0x2 : arg_len = (4 + *argLen);\r\n43\r\n44 *ptrCurrentOffset += arg_len;\r\n45\r\n46 ...\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 3 of 15\n\nFinally, (and most problematic) though the file events delivered via /dev/fsevents contain information about\r\nthe process responsible for generating the file event ( struct kfs_event_a ), this information is simply a process\r\nidentifier ( pid ):\r\n1typedef struct kfs_event_a {\r\n2 uint16_t type;\r\n3 uint16_t refcount;\r\n4 pid_t pid;\r\n5} kfs_event_a;\r\nWhen building a comprehensive file monitor (especially as part of a security tool), one generally requires more\r\ninformation about the responsible process, such as its path and code-signing information.\r\nGenerating code-signing process via pid, is (somewhat) non-trivial and may also be rather computationally (CPU)\r\nintensive.\r\nAlthough there exist APIs (such as proc_pidpath ) to generate more comprehensive process information solely\r\nfrom a pid , such APIs unsurprisingly fail if the process as (already) terminated. As there is some inherent delay\r\nin file events delivered via /dev/fsevents , this is actually not uncommon (think malware installers that simply\r\npersist a binary then (quickly) exit).\r\nWorse, if the pid is reused one may actually mis-identify the process that generated the file event. For a security\r\nproduct, this is rather unacceptable! (For other “issues” with pids see: “Don’t Trust the PID! ”).\r\nIt is also possible to receive file I/O events via the OpenBSM subsystem. However, there are limitations to this\r\napproach as well, as we highlighted in previous blog post.\r\nAs such, until now, the only way to realize a truly effective file monitor was via code running in ring-0 (the\r\nkernel).\r\nApple’s Endpoint Security Framework\r\nWith Apple’s push to kick 3rd-party developers (including security products) out of the kernel, coupled with the\r\nrealization (finally!) that the existing subsystems were rather archaic and dated, Apple recently announced the\r\nnew, user-mode “Endpoint Security Framework” (that provides a user-mode interface to a new “Endpoint Security\r\nSubsystem”).\r\nAs we’ll see, this framework addresses many of the aforementioned issues \u0026 shortcomings!\r\nSpecifically it provides a:\r\nwell-defined and (relatively) simple API\r\ncomprehensive process (including code-signing), information for all events\r\nthe ability to proactively respond to file events (though here, our file monitor will be passive).\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 4 of 15\n\nI’m often somewhat critical of Apple’s security posture (or lack thereof). However, the “Endpoint Security\nFramework” is potentially a game-changer for those of us seeking to write robust user-mode security tools for\nmacOS. Mahalo Apple! Personally I’m stoked 🥳\nThis blog is practical walk-thru of creating a file monitor which leverages Apple’s new framework. For more\ninformation on the Endpoint Security Framework, see Apple’s developer documentation:\nEndpoint Security Framework\nIn this blog, we’ll illustrate exactly how to create a comprehensive user-mode file monitor that leverages Apple’s\nnew framework.\nAs noted in our previous blog post there are a few prerequisites to leverage the Endpoint Security Framework that\ninclude:\nThe com.apple.developer.endpoint-security.client entitlement\nThis can be requested from Apple via this link. Until then (I’m still waiting 😅), give yourself that\nentitlement (i.e. in your app’s $(ProductName).entitlements file, and disable SIP such that it remains\npseudo-unenforced).\ncom.apple.developer.endpoint-security.client Xcode 11/macOS 10.15 SDK\nAs these are both (still) in beta, for now, it’s recommended to perform development in a virtual machine\n(running macOS 10.15, beta).\nmacOS 10.15 (Catalina)\nIt appears the Endpoint Security Framework will not be made available to older versions of macOS. As\nsuch, any tools the leverage this framework will only run on 10.15 or newer.\nOk enough chit-chat, let’s dive in!\nOur goal is simple: create a comprehensive user-mode file monitor that leverages Apple’s new “Endpoint Security\nFramework”.\nBesides “capturing” file I/O events, we’re also interested in:\nthe type of event (create, write, etc.)\nthe path(s) of the file ((possibly) source and destination)\nthe process responsible for the event, including its:\nprocess id (pid)\nhttps://objective-see.com/blog/blog_0x48.html\nPage 5 of 15\n\nprocess path\r\nany process code-signing information\r\n…luckily, unlike reading events off /dev/fsevents the new Endpoint Security framework makes this a breeze!\r\nAs noted in our previous blog post, in order to subscribe to events from the “Endpoint Security Subsystem”, we\r\nmust first create a new “Endpoint Security” client. The es_new_client function provides the interface to perform\r\nthis action:\r\nIn code, we first include the EndpointSecurity.h file, declare a global variable (type: es_client_t* ), then\r\ninvoke the es_new_client function:\r\n 1#import \u003cEndpointSecurity/EndpointSecurity.h\u003e\r\n 2\r\n 3//(global) endpoint client\r\n 4es_client_t* endpointClient = nil;\r\n 5\r\n 6//create client\r\n 7// callback invokes (user) callback for new processes\r\n 8es_new_client(\u0026endpointClient, ^(es_client_t *client, const es_message_t *message)\r\n 9{\r\n10 //process events\r\n11\r\n12});\r\nNote that the es_new_client function takes an (out) pointer to the variable of type es_client_t . Once the\r\nfunction returns, this variable will hold the initialized endpoint security client (required by all other endpoint\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 6 of 15\n\nsecurity APIs). The second parameter of the es_new_client function is a block that will be automatically\r\ninvoked on endpoint security events (more on this shortly!)\r\nIf all is well, the es_new_client function will return ES_NEW_CLIENT_RESULT_SUCCESS indicating that it\r\nhas created a newly initialized Endpoint Security client ( es_client_t ) for us to use.\r\nTo compile the above code, link against the Endpoint Security Framework (libEndpointSecurity)\r\nOnce we’ve created an instance of a es_new_client , we now must tell the Endpoint Security subsystem what\r\nevents we are interested in (or want to “subscribe to”, in Apple parlance). This is accomplished via the\r\nes_subscribe function (documented here and in the ESClient.h header file):\r\nThis function takes the initialized endpoint client (returned by the es_new_client function), an array of events of\r\ninterest, and the size of said array:\r\n 1//(process) events of interest\r\n 2es_event_type_t events[] = {\r\n 3 ES_EVENT_TYPE_NOTIFY_CREATE,\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 7 of 15\n\n4 ES_EVENT_TYPE_NOTIFY_WRITE,\r\n 5 ...\r\n 6};\r\n 7\r\n 8//subscribe to events\r\n 9if(ES_RETURN_SUCCESS != es_subscribe(endpointClient, events,\r\n10 sizeof(events)/sizeof(events[0])))\r\n11{\r\n12 //err msg\r\n13 NSLog(@\"ERROR: es_subscribe() failed\");\r\n14\r\n15 //bail\r\n16 goto bail;\r\n17}\r\nThe events of interest depends on well, what events are of interest to you! As we’re writing a file monitor we’re\r\n(only) interested in file-related events such as:\r\nES_EVENT_TYPE_NOTIFY_CREATE\r\n“A type that represents file creation notification events.”\r\nES_EVENT_TYPE_NOTIFY_OPEN\r\n“A type that represents file opening notification events.”\r\nES_EVENT_TYPE_NOTIFY_WRITE\r\n“A type that represents file writing notification events.”\r\nES_EVENT_TYPE_NOTIFY_CLOSE\r\n“A type that represents file closing notification events.”\r\nES_EVENT_TYPE_NOTIFY_RENAME\r\n“A type that represents file renaming notification events.”\r\nES_EVENT_TYPE_NOTIFY_LINK\r\n“A type that represents link creation notification events.”\r\nES_EVENT_TYPE_NOTIFY_UNLINK\r\n“A type that represents link unlinking notification events.”\r\nSee Apple's [documentation](https://developer.apple.com/documentation/endpointsecurity/es_event_type_t?\r\nlanguage=objc) for other events types in the `es_event_type_t` enumeration.\r\nOnce the es_subscribe function successfully returns ( ES_RETURN_SUCCESS ), the Endpoint Security subsystem\r\nwill start delivering messages (read: file events).\r\nRecall that the final argument of the es_new_client function is a callback block (or handler). Apple states: “The\r\nhandler block…will be run on all messages sent to this client.”\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 8 of 15\n\nThe block is invoked with the endpoint client, and most importantly the message from the Endpoint Security\r\nsubsystem. This message variable is a pointer of type es_message_t (i.e. es_message_t* ).\r\nNotable members of the es_message_t include:\r\nes_process_t * process\r\nA pointer to a structure that describes the process responsible for the file event.\r\nes_event_type_t event_type\r\nThe type of event (that will match one of the events we subscribed to, e.g.\r\nES_EVENT_TYPE_NOTIFY_CREATE )\r\nevent_type event\r\nAn event specific structure (i.e. es_event_create_t )\r\nThough the event member of the message structure ( message-\u003eevent ) is event specific, for all file events it is\r\nlargely the same. For example, compare the es_event_write_t and es_event_create_t structures:\r\n$ less MacOSX10.15.sdk/usr/include/EndpointSecurity/ESMessage.h\r\ntypedef struct {\r\n es_file_t * _Nullable target;\r\n uint8_t reserved[64];\r\n} es_event_write_t;\r\ntypedef struct {\r\n es_file_t * _Nullable target;\r\n uint8_t reserved[64];\r\n} es_event_create_t;\r\nBoth contain a pointer to a es_file_t structure, which contains a path to the file (created, written to, etc.):\r\n$ less MacOSX10.15.sdk/usr/include/EndpointSecurity/ESMessage.h\r\n/**\r\n * es_file_t provides the inode/devno \u0026 path to a file that relates to a security event\r\n * the path may be truncated, which is indicated by the path_truncated flag.\r\n */\r\ntypedef struct {\r\n es_string_token_t path;\r\n bool path_truncated;\r\n union {\r\n dev_t devno;\r\n fsid_t fsid;\r\n };\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 9 of 15\n\nino64_t inode;\r\n} es_file_t;\r\nNote however, some file events (such as the es_event_rename_t event) involve both a source and destination\r\nfile:\r\ntypedef struct {\r\n es_file_t * _Nullable source;\r\n es_destination_type_t destination_type;\r\n union {\r\n es_file_t * _Nullable existing_file;\r\n struct {\r\n es_file_t * _Nullable dir;\r\n es_string_token_t filename;\r\n } new_path;\r\n } destination;\r\n uint8_t reserved[64];\r\n} es_event_rename_t;\r\nAll file events messages also contain a pointer to a es_process_t structure ( message-\u003eprocess ) for the\r\nresponsible process (i.e. that generated the file event). As detailed in our previous post, this structure contains full\r\nprocess information (including pid, path, and code-signing information).\r\nAt this point we’re stoked as we’re receiving all file events along with full details about the responsible process.\r\n(No more worrying about pid lookups failing or returning the incorrect process!) 😅\r\nLet’s now look illustrate this in code.\r\nFirst, in the es_new_client message callback, we instantiate a (custom) File object, passing in the received\r\nes_message_t message:\r\n 1//create client\r\n 2// callback invoked on file events\r\n 3es_new_client(\u0026endpointClient, ^(es_client_t *client, const es_message_t *message)\r\n 4{\r\n 5 //new file obj\r\n 6 File* file = nil;\r\n 7\r\n 8 //init file obj\r\n 9 file = [[File alloc] init:(es_message_t* _Nonnull)message];\r\n10 if(nil != file)\r\n11 {\r\n12 //invoke user callback\r\n13 callback(file);\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 10 of 15\n\n14 }\r\n15});\r\nThis (custom) File object’s init: method simply parses out relevant information from the es_process_t\r\nstructure (such as process id, path, and code-signing information as detailed in our previous post), and then\r\nextracts the file path(s):\r\n1//set process\r\n2self.process = [[Process alloc] init:message];\r\n3\r\n4//extract path(s)\r\n5// logic is specific to event\r\n6[self extractPaths:message];\r\nThe extractPaths: method contains event specific logic (as recall some, but not all, file events contain both a\r\nsource and destination path):\r\n 1//extract source \u0026 destination path\r\n 2// this requires event specific logic\r\n 3-(void)extractPaths:(es_message_t*)message\r\n 4{\r\n 5 //event specific logic\r\n 6 switch (message-\u003eevent_type) {\r\n 7\r\n 8 //create\r\n 9 case ES_EVENT_TYPE_NOTIFY_CREATE:\r\n10 self.destinationPath = convertStringToken(\u0026message-\u003eevent.create.target-\u003epath);\r\n11 break;\r\n12\r\n13 //write\r\n14 case ES_EVENT_TYPE_NOTIFY_WRITE:\r\n15 self.destinationPath = convertStringToken(\u0026message-\u003eevent.write.target-\u003epath);\r\n16 break;\r\n17\r\n18 ...\r\n19\r\n20 //rename\r\n21 case ES_EVENT_TYPE_NOTIFY_RENAME:\r\n22\r\n23 //set (src) path\r\n24 self.sourcePath = convertStringToken(\u0026message-\u003eevent.rename.source-\u003epath);\r\n25\r\n26 //existing file ('ES_DESTINATION_TYPE_EXISTING_FILE')\r\n27 if(ES_DESTINATION_TYPE_EXISTING_FILE == message-\u003eevent.rename.destination_type)\r\n28 {\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 11 of 15\n\n29 //set (dest) file\r\n30 self.destinationPath = convertStringToken(\u0026message-\u003eevent.rename.destination.existing_file-\u003epath);\r\n31 }\r\n32 //new path ('ES_DESTINATION_TYPE_NEW_PATH')\r\n33 else\r\n34 {\r\n35 //set (dest) path\r\n36 // combine dest dir + dest file\r\n37 self.destinationPath = [convertStringToken(\u0026message-\u003eevent.rename.destination.new_path.dir-\u003epath)\r\n38 }\r\n39\r\n40 break;\r\n41\r\n42 ...\r\n43 }\r\n44\r\n45 return;\r\n46}\r\nOnce the File object’s init: method returns, we have a comprehensive (and fully parsed) representation of\r\nthe reported file event.\r\nFile Monitor Library\r\nAs noted, several of Objective-See’s tools track file events but currently do so via inefficient and (now) antiquated\r\nmeans.\r\nLucky us, as shown in this blog, we can now leverage Apple’s Endpoint Security Subsystem to effectively and\r\ncomprehensively monitor file events (from user-mode!).\r\nAs such, today, I’m releasing an open-source file monitoring library, that implements everything we’ve discussed\r\nhere today 🥳\r\nIt’s fairly simple to leverage this library in your own (non-commercial) tools:\r\n1. Build the library, libFileMonitor.a\r\n2. Add the library and its header file ( FileMonitor.h ) to your project:\r\n#import \"FileMonitor.h\"\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 12 of 15\n\nAs shown above, you’ll also have link against the libbsm (for audit_token_to_pid ) and\r\nlibEndpointSecurity libraries.\r\n3. Add the com.apple.developer.endpoint-security.client entitlement (to your project’s\r\n$(ProductName).entitlements file).\r\n4. Write some code to interface with the library!\r\nThis final steps involves instantiating a FileMonitor object and invoking the start method (passing in\r\na callback block that’s invoked on file events). Below is some sample code that implements this logic:\r\n 1//init monitor\r\n 2FileMonitor* fileMon = [[FileMonitor alloc] init];\r\n 3\r\n 4//define block\r\n 5// automatically invoked upon file events\r\n 6FileCallbackBlock block = ^(File* file)\r\n 7{\r\n 8 switch(file.event)\r\n 9 {\r\n10 //create\r\n11 case ES_EVENT_TYPE_NOTIFY_CREATE:\r\n12 NSLog(@\"FILE CREATE ('ES_EVENT_TYPE_NOTIFY_CREATE')\");\r\n13 break;\r\n14\r\n15 //write\r\n16 case ES_EVENT_TYPE_NOTIFY_WRITE:\r\n17 NSLog(@\"FILE WRITE ('ES_EVENT_TYPE_NOTIFY_WRITE')\");\r\n18 break;\r\n19\r\n20 //print info\r\n21 NSLog(@\"%@\", file);\r\n22};\r\n23\r\n24//start monitoring\r\n25// pass in block for events\r\n26[fileMon start:block];\r\n27\r\n28//run loop\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 13 of 15\n\n29// as don't want to exit\r\n30[[NSRunLoop currentRunLoop] run];\r\nOnce the [fileMon start:block]; method has been invoked, the File Monitoring library will automatically\r\ninvoke the callback ( block ), on file events, returning a File object.\r\nThe File object is declared in the library’s header file; FileMonitor.h . This object contains information about\r\nthe file event ((possibly) source and destination path) and the process responsible for the event (in a Process\r\nobject). Take a peek at the FileMonitor.h file for more details.\r\nOnce compiled, we’re ready to start monitoring for file events!\r\nFor example, we run: $ echo \"objective-see rules\" \u003e /tmp/test , which generates an open, write, and close\r\nfile I/O events:\r\n# ./fileMonitor\r\nStarting file monitor...[ok]\r\nFILE OPEN ('ES_EVENT_TYPE_NOTIFY_OPEN')\r\nsource path: (null)\r\ndestination path: /private/tmp/test\r\nprocess: pid: 649\r\npath: /bin/zsh\r\nuid: 501\r\nsigning info: {\r\n cdHash = BD67298030CA90256B3999A118DCF2FFE5352A9E;\r\n csFlags = 603996161;\r\n isPlatforBinary = 1;\r\n signatureIdentifier = \"com.apple.zsh\";\r\n}\r\nFILE WRITE ('ES_EVENT_TYPE_NOTIFY_WRITE')\r\nsource path: (null)\r\ndestination path: /private/tmp/test\r\nprocess: pid: 649\r\npath: /bin/zsh\r\nuid: 501\r\nsigning info: {\r\n cdHash = BD67298030CA90256B3999A118DCF2FFE5352A9E;\r\n csFlags = 603996161;\r\n isPlatforBinary = 1;\r\n signatureIdentifier = \"com.apple.zsh\";\r\n}\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 14 of 15\n\nFILE CLOSE ('ES_EVENT_TYPE_NOTIFY_CLOSE')\r\nsource path: (null)\r\ndestination path: /private/tmp/test\r\nprocess: pid: 649\r\npath: /bin/zsh\r\nuid: 501\r\nsigning info: {\r\n cdHash = BD67298030CA90256B3999A118DCF2FFE5352A9E;\r\n csFlags = 603996161;\r\n isPlatforBinary = 1;\r\n signatureIdentifier = \"com.apple.zsh\";\r\n}\r\nConclusion\r\nPreviously, writing a (user-mode) file monitor for macOS was not a trivial task. Thanks to Apple’s new Endpoint\r\nSecurity framework/subsystem (on macOS 10.15+), it’s now a breeze!\r\nIn short, one simply invokes the es_new_client \u0026 es_subscribe functions to subscribe to events of interest\r\n(recalling that the com.apple.developer.endpoint-security.client entitlement is required).\r\nFor a file monitor, we illustrated how to subscribe to the file-related events such as:\r\nES_EVENT_TYPE_NOTIFY_CREATE\r\nES_EVENT_TYPE_NOTIFY_OPEN\r\nES_EVENT_TYPE_NOTIFY_WRITE\r\nWe then showed how to extract the relevant file and (responsible) process structures and parse out all relevant\r\nmeta-data.\r\nFinally we discussed an open-source file monitoring library that implements everything we’ve discussed here\r\ntoday. 🥳 \\\r\n❤️ Love these blog posts and/or want to support my research and tools? \\ You can support them via my [Patreon]\r\n(https://www.patreon.com/bePatron?c=701171) page! \\\r\nSource: https://objective-see.com/blog/blog_0x48.html\r\nhttps://objective-see.com/blog/blog_0x48.html\r\nPage 15 of 15",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://objective-see.com/blog/blog_0x48.html"
	],
	"report_names": [
		"blog_0x48.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434458,
	"ts_updated_at": 1775791244,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1b0dfd07316e8f8db1ea2db5aa2be2930a3a7e9c.pdf",
		"text": "https://archive.orkl.eu/1b0dfd07316e8f8db1ea2db5aa2be2930a3a7e9c.txt",
		"img": "https://archive.orkl.eu/1b0dfd07316e8f8db1ea2db5aa2be2930a3a7e9c.jpg"
	}
}