An isolated executor is the only type allowed to touch files, network, or long work. Tools call the executor. They do not call FileManager or URLSession directly. That gives you one place to enforce the box.
struct ToolRequest: Sendable {
var name: String
var fileIDs: [String]
var url: URL?
var work: @Sendable () async throws -> String
}
enum ExecutorError: Error {
case unknownTool
case fileNotAllowed
case networkDenied
case timeout
}
actor ToolExecutor {
var allowedFiles: Set<String>
var networkAllowed: Set<String>
var timeout: Duration = .seconds(8)
func run(_ request: ToolRequest) async throws -> String {
guard allowedFiles.isSuperset(of: request.fileIDs) else {
throw ExecutorError.fileNotAllowed
}
if let url = request.url, !networkAllowed.contains(request.name) {
throw ExecutorError.networkDenied
}
return try await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await request.work() }
group.addTask {
try await Task.sleep(for: timeout)
throw ExecutorError.timeout
}
let value = try await group.next()!
group.cancelAll()
return value
}
}
}
On macOS, work can be an XPC call instead of local file code. The executor still checks the allow-list first. The helper should refuse paths the UI process did not send as security-scoped bookmarks.
#if os(macOS)
func readInHelper(_ bookmark: Data) async throws -> String {
let connection = NSXPCConnection(serviceName: "dev.edgefde.ToolHelper")
connection.remoteObjectInterface = NSXPCInterface(with: ToolHelperProtocol.self)
connection.resume()
// Call the helper through the typed protocol. The helper opens the bookmark.
return try await HelperClient(connection: connection).read(bookmark)
}
#endif
Confirm XPC API names in the installed SDK. The sketch shows the shape: a named service, a protocol, and a bookmark. It is not a full helper project.
Read Talk to tools with MCP, Stop prompt attacks and leaks, and Make several agents agree first if the request arguments themselves are the attack.
Next, set limits the executor always applies.
Key concepts
- The executor actor is the only type that touches files, network, or long work.
- A request carries name, file ids, optional url, and a work closure.
- Errors include unknown tool, file not allowed, network denied, and timeout.
- Timeout by racing sleep in a task group, then cancel.
Takeaways
- Tools call the executor, never
FileManagerorURLSessiondirectly. - One executor gives one enforcement point.
- If a tool can open a file without the executor, the box is optional.