Creating enhanced security helper extensions
Reduce opportunities for an attacker to target your app through its extensions.
Overview
Separating sensitive calculations, such as handling data from untrusted sources, into extensions that the system runs as separate processes from your app reduces the opportunity for attackers to target the sensitive calculation and gain access to other parts of your app or the system. Create Enhanced Security helper extensions on iOS, iPadOS, and macOS to enforce tighter sandbox restrictions, and enable compiler and runtime security protections.
Define the extension point in your app
Add the EX_ENABLE_EXTENSION_POINT_GENERATION user-defined build setting with the value YES to your app target’s build setting. In your app’s source code, create an extension to AppExtensionPoint that defines your app’s extension point. Give the extension point the AppExtensionPoint.Name of your choosing, and the AppExtensionPoint.EnhancedSecurity attribute to tell the system to run your extension in the Enhanced Security runtime:
import ExtensionFoundation
extension AppExtensionPoint {
@Definition
static var exampleExtension: AppExtensionPoint {
Name("exampleExtension")
EnhancedSecurity()
}
}Create the extension
Follow these steps to create the extension:
In Xcode, choose File > New > Target, and select the Generic Extension template.
Select the Enhanced Security Extension extension type, and click Finish.
In the Xcode Project navigator, delete the extension target’s
Info.plistfile.In the target build settings editor, delete the
INFOPLIST_FILEbuild setting for the extension target.
Edit the main source file for the extension, so that the AppExtensionPoint.Identifier for the extension point it binds to has your app’s bundle ID as the host identifier, and the name you gave the extension point in the previous section:
import ExtensionFoundation
@main
struct MyAppHelper: ExampleExtension {
@AppExtensionPoint.Bind
var boundExtensionPoint: AppExtensionPoint {
AppExtensionPoint.Identifier(host: "com.example.my-app", name: "exampleExtension")
}
}Write the code to implement your extension’s behavior. The system runs your extension in a sandbox that restricts access to most system services and frameworks. In particular, your extension can’t present UI. Instead, it must communicate with its host app, and ask the app to present UI and interact with other system services on the extension’s behalf.
Discover and launch the extension
In your app, create an AppExtensionPoint.Monitor to discover the Enhanced Security extension:
let monitor = try await AppExtensionPoint.Monitor(appExtensionPoint: AppExtensionPoint.exampleExtension)
guard let identity = monitor.identities.first else {
fatalError("Extension not found")
} The identity represents the bundle for the Enhanced Security extension you created in the previous section. Create an AppExtensionProcess with that bundle to launch the extension:
do {
self.process = try await AppExtensionProcess(configuration: .init(appExtensionIdentity: identity, onInterruption: {
// The system calls this closure when the extension exits.
}))
// Communicate with the extension.
}
catch let error {
// The system can't launch the extension.
}Communicate between the app and the extension
Use XPCSession to handle communication between your app and its Enhanced Security extension. For more information, see Creating XPC services.
Define structures that conform to Codable in code you share between the app and extension, and create instances of those structures to send over XPC using the session you create:
struct Message: Identifiable, Codable {
var id: String
// Add properties that represent data your app sends to its extension.
struct Response: Codable {
// Define another structure that the extension sends back to the app in its reply.
}
}Call makeXPCSession() in your app to create an XPCSession you use to send messages to the extension:
do {
self.xpcSession = try process.makeXPCSession()
try xpcSession.activate()
// Send messages to the extension.
}
catch let error {
// The system can't create the XPC session.
}Use the session you create to send messages to the extension, and receive responses:
let response = try await withCheckedThrowingContinuation { continuation in
do {
// Construct the message the app sends to the extension.
let message = Message()
try xpcSession.send(Message()) { result in
switch result {
case .success(let reply):
let response = (try? reply.decode(as: Message.Response.self)) ?? /* Create a default response. */
continuation.resume(returning: response)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
catch {
continuation.resume(throwing: error)
}
}
// Interpret the response from the extension.In the extension, create a ConnectionHandler to accept incoming connections from the app. Call the init(onSessionRequest:) initializer, and use the closure to accept the XPC connection and create a session.
extension ExampleExtension {
var configuration: some AppExtensionConfiguration {
// Return your extension's configuration upon request.
return ConnectionHandler(onSessionRequest: { request in
let handler = MessageHandler(appExtension: self)
return request.accept { session in
return handler
}
})
}
}
fileprivate struct MessageHandler<E: ExampleExtension>: XPCPeerHandler, Sendable {
let appExtension: E
func handleIncomingRequest(_ message: Message) -> (any Encodable)? {
// Process the incoming message.
return Message.Response()
}
}
Adopt other security hardening features
Enhanced security extensions use the same compiler and runtime security features as apps that adopt the Enhanced Security capability. For more information, see Enabling enhanced security for your app.