Introduction
Swift Package Manager plugins run in a sandboxed environment with restricted file system and network access. When a plugin attempts an operation outside its granted permissions, it fails with a sandbox violation error. This affects both build tool plugins and command plugins, and the available permissions depend on the plugin type and Swift version.
Symptoms
sandbox-exec: operation not permittedduring plugin executionerror: plugin requires permission to access file- Plugin works with
--disable-sandboxbut fails without it - Build tool plugin cannot read source files outside its package directory
- Command plugin fails to write output files
Example error:
``
error: plugin 'GenerateCodePlugin' needs to be able to write to the
package directory in order to write output files. Add the following
to the plugin's declaration:
permissions: [.writeToPackageDirectory(reason: "Generate source files")]
Common Causes
- Plugin needs to write files but no write permission declared
- Plugin tries to access files outside the package directory
- Build tool plugin needs network access to download resources
- Plugin executes an external tool not in the allowed list
- Sandbox permissions API changed between Swift toolchain versions
Step-by-Step Fix
- 1.Declare required permissions in plugin manifest:
- 2.```swift
- 3.// Package.swift
- 4.let package = Package(
- 5.name: "MyPackage",
- 6.plugins: [
- 7..plugin(
- 8.name: "GenerateCodePlugin",
- 9.capability: .buildTool(),
- 10.// Permissions for Swift 5.9+
- 11.permissions: [
- 12..writeToPackageDirectory(
- 13.reason: "Generate source code from templates"
- 14.)
- 15.]
- 16.)
- 17.]
- 18.)
- 19.
` - 20.Use the correct plugin capability:
- 21.```swift
- 22.import PackagePlugin
// Build tool plugin: runs during build, limited sandbox struct GenerateCodePlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputDir = context.pluginWorkDirectory return [ .buildCommand( displayName: "Generating code", executable: try context.tool(named: "generator").path, arguments: ["--output", outputDir.string], outputFiles: [outputDir.appending("Generated.swift")] ) ] } }
// Command plugin: runs on demand, can request more permissions struct FormatPlugin: CommandPlugin { func performCommand(context: PluginContext, arguments: [String]) async throws { // Has broader access when run manually let sourceDir = context.package.directory // Can read/write within the package directory } } ```
- 1.Work around sandbox limitations with pre-defined output:
- 2.```swift
- 3.// Instead of writing to arbitrary locations, use pluginWorkDirectory
- 4.struct MyPlugin: BuildToolPlugin {
- 5.func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
- 6.let outputDir = context.pluginWorkDirectory
- 7.let outputFile = outputDir.appending("Generated.swift")
return [ .prebuildCommand( displayName: "Generate Swift code", executable: try context.tool(named: "codegen").path, arguments: ["--output", outputFile.string], outputFilesDirectory: outputDir ) ] } } ```
- 1.Debug with sandbox disabled (development only):
- 2.```bash
- 3.# For command plugins
- 4.swift package --disable-sandbox my-command
# For build tool plugins, add to Package.swift: # .plugin(name: "MyPlugin", package: "MyPluginPackage") # Then build with: swift build -v # Verbose output shows sandbox violations ```
Prevention
- Declare all required permissions in the plugin manifest
- Use
pluginWorkDirectoryfor all output files - Test plugins both with and without sandbox
- Keep plugin dependencies minimal to reduce permission needs
- Document required permissions in the plugin README
- Use
context.loggerto debug file paths during plugin execution