Skip to content

Instantly share code, notes, and snippets.

@andriitishchenko
Last active August 8, 2025 23:48
Show Gist options
  • Select an option

  • Save andriitishchenko/c0442ed3b8ae8ed5570534c846a83b1c to your computer and use it in GitHub Desktop.

Select an option

Save andriitishchenko/c0442ed3b8ae8ed5570534c846a83b1c to your computer and use it in GitHub Desktop.
Simple Endpoint Security system extension to receive events
// hostApp.swift
// Main app (host app)
/*
// entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
*/
/*
CHANGE TO YOUR ID:
1. com.example.esextension
2. esextension.appex
*/
import SwiftUI
import SystemExtensions
import os
@main
struct esseApp: App {
@StateObject private var installer = SystemExtensionInstaller.shared
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
installer.installExtension()
}
}
}
}
final class SystemExtensionInstaller: NSObject, OSSystemExtensionRequestDelegate, ObservableObject {
static let shared = SystemExtensionInstaller()
let log = OSLog(subsystem: "com.example", category: "hostApp")
func installExtension() {
guard let _ = Bundle.main.builtInPlugInsURL?.appendingPathComponent("esextension.appex") else {
print("Extension not found")
return
}
let request = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: "com.example.esextension", queue: .main)
request.delegate = self
OSSystemExtensionManager.shared.submitRequest(request)
}
func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) {
print("Extension installed: \(result)")
os_log("Extension installed: %{public}@", log: log, type: .info, String(describing: result))
}
func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) {
print("Extension installation failed: \(error)")
os_log("Extension installation failed: %@", log: log, type: .error, error.localizedDescription)
}
func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
// pop "bla-bla would like to install a new endpoint" to Login & Extensions [OK] [Open System Settings]
// manually approve it Settings -> search "extensions" -> "Endpoint Security Extensions" -> Enable
print("User approval needed to install extension")
os_log("User approval needed to install extension", log: log, type: .info)
}
func request(_ request: OSSystemExtensionRequest,
actionForReplacingExtension existing: OSSystemExtensionProperties,
withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction {
return .replace
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello")
.font(.headline)
.padding()
}
}
}
// main.swift
// Endpoint Security Extension Code
// This is an Endpoint Security extension to demonstrate basic functionality.
//
// https://developer.apple.com/documentation/endpointsecurity
// Instructions:
// https://developer.apple.com/documentation/endpointsecurity/monitoring-system-events-with-endpoint-security
// It requires Apple approval and is associated with the host application "com.example.esextension".
// To create a client, your app must have the com.apple.developer.endpoint-security.client entitlement.
// The user also needs to approve your app with Transparency, Consent, and Control (TCC) mechanisms.
// The user does this in the Security and Privacy pane of System Preferences, by adding the app to Full Disk Access.
/*
Requaried frameworks:
- libbsm
- libEndpointSecurity
- SystemExtension
- UniformTypeIdentifiers
### Expected Behavior
1. **Block TextEdit:** The extension is expected to block the launch of the TextEdit application.
2. **EICAR Signature Block:** It will deny access to files containing the EICAR antivirus test signature.
**Test Command:**
`echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > /tmp/test_virus_signature.txt`
3. **System Directory Protection:** Writing to the `/usr/local/bin/` directory will be prevented.
**Test Command:**
`echo 'test' > /usr/local/bin/test_virus_signature.txt`
To debug, use the **Console.app** and filter by the process name "com.example.esextension".
*/
/*
// entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
</dict>
</plist>
*/
import Foundation
import EndpointSecurity
import os.log
// OSLog Logger
let log = OSLog(subsystem: "com.example.esextension", category: "esextension")
let gEventQueue = DispatchQueue(label: "com.example.esextension.eventQueue", qos: .userInitiated, attributes: .concurrent)
// MARK: - Helpers
func signingID(_ token: es_string_token_t) -> String? {
let data = Data(bytes: token.data, count: Int(token.length))
return String(data: data, encoding: .utf8)
}
func filePath(_ file: UnsafePointer<es_file_t>) -> String {
String(cString: file.pointee.path.data)
}
// MARK: - EICAR Check
func isEicarFile(_ file: UnsafePointer<es_file_t>) -> Bool {
let eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
let eicarData = Data(eicar.utf8)
let eicarLength = eicarData.count
let eicarMaxLength = 128
let path = filePath(file)
guard file.pointee.stat.st_size >= eicarLength,
file.pointee.stat.st_size <= eicarMaxLength
else { return false }
let fileURL = URL(filePath: path)
guard let fileData = try? Data(contentsOf: fileURL, options: .mappedIfSafe) else { return false }
return fileData.prefix(eicarLength) == eicarData
}
// MARK: - Event Handlers
func handleExec(_ client: OpaquePointer, _ msg: UnsafePointer<es_message_t>) {
guard let id = signingID(msg.pointee.event.exec.target.pointee.signing_id) else {
es_respond_auth_result(client, msg, ES_AUTH_RESULT_ALLOW, true)
return
}
if id == "com.apple.TextEdit" {
os_log("Blocked execution: %{public}@", log: log, type: .info, id)
es_respond_auth_result(client, msg, ES_AUTH_RESULT_DENY, true)
} else {
es_respond_auth_result(client, msg, ES_AUTH_RESULT_ALLOW, true)
}
}
func handleOpenWorker(_ client: OpaquePointer, _ msg: UnsafePointer<es_message_t>) {
let file = msg.pointee.event.open.file
let path = filePath(file)
if isEicarFile(file) {
os_log("Denied open (EICAR): %{public}@", log: log, type: .info, path)
es_respond_flags_result(client, msg, 0, true)
} else if path.hasPrefix("/usr/local/bin/") {
os_log("Restricted write: %{public}@", log: log, type: .info, path)
es_respond_flags_result(client, msg, UInt32(O_RDONLY), true)
} else {
es_respond_flags_result(client, msg, UInt32.max, true)
}
}
func handleOpen(_ client: OpaquePointer, _ msg: UnsafePointer<es_message_t>) {
es_retain_message(msg)
gEventQueue.async {
handleOpenWorker(client, msg)
es_release_message(msg)
}
}
// MARK: - Main Handler Block
let handleEvent: es_handler_block_t = { (client, msg) in
switch msg.pointee.event_type {
case ES_EVENT_TYPE_AUTH_EXEC:
handleExec(client, msg)
case ES_EVENT_TYPE_AUTH_OPEN:
handleOpen(client, msg)
default:
if msg.pointee.action_type == ES_ACTION_TYPE_AUTH {
es_respond_auth_result(client, msg, ES_AUTH_RESULT_ALLOW, true)
} else {
os_log("Unexpected event type: %d", log: log, type: .error, msg.pointee.event_type.rawValue)
}
}
}
// MARK: - Client creation result enum
enum ESClientCreationResult: CustomStringConvertible {
case success
case notEntitled
case notPrivileged
case notPermitted
case invalidArgument
case tooManyClients
case internalError
case unknown(Int32)
init(esResult: es_new_client_result_t) {
switch esResult {
case ES_NEW_CLIENT_RESULT_SUCCESS:
self = .success
case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED:
self = .notEntitled
case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED:
self = .notPrivileged
case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
self = .notPermitted
case ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT:
self = .invalidArgument
case ES_NEW_CLIENT_RESULT_ERR_TOO_MANY_CLIENTS:
self = .tooManyClients
case ES_NEW_CLIENT_RESULT_ERR_INTERNAL:
self = .internalError
default:
self = .unknown(Int32(esResult.rawValue))
}
}
var description: String {
switch self {
case .success: return "Success"
case .notEntitled: return "Missing entitlement"
case .notPrivileged: return "Not running as root"
case .notPermitted: return "TCC permission denied"
case .invalidArgument: return "Invalid argument"
case .tooManyClients: return "Too many clients"
case .internalError: return "Internal error"
case .unknown(let code): return "Unknown error code: \(code)"
}
}
}
func start() {
var clientPtr: OpaquePointer?
let newClientRaw = es_new_client(&clientPtr, handleEvent)
let newClientResult = ESClientCreationResult(esResult: newClientRaw)
guard case .success = newClientResult else {
os_log("%{public}@", type: .fault, newClientResult.description)
if case .notPermitted = newClientResult {
try? Process.run(
URL(fileURLWithPath: "/usr/bin/open"),
arguments: ["x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"])
}
exit(EXIT_FAILURE)
}
guard let client = clientPtr else {
os_log("Client pointer is nil after success.", log: log, type: .fault)
exit(EXIT_FAILURE)
}
defer {
es_delete_client(client)
}
var events: [es_event_type_t] = [ES_EVENT_TYPE_AUTH_EXEC, ES_EVENT_TYPE_AUTH_OPEN]
let subscribeResult = es_subscribe(client, &events, UInt32(events.count))
if subscribeResult != ES_RETURN_SUCCESS {
os_log("Client failed to subscribe to events. Code: %d", log: log, type: .error, subscribeResult.rawValue)
exit(EXIT_FAILURE)
}
os_log("Client initialized and subscribed successfully.", log: log, type: .info)
dispatchMain()
}
start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment