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 permitted during plugin execution
  • error: plugin requires permission to access file
  • Plugin works with --disable-sandbox but 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. 1.Declare required permissions in plugin manifest:
  2. 2.```swift
  3. 3.// Package.swift
  4. 4.let package = Package(
  5. 5.name: "MyPackage",
  6. 6.plugins: [
  7. 7..plugin(
  8. 8.name: "GenerateCodePlugin",
  9. 9.capability: .buildTool(),
  10. 10.// Permissions for Swift 5.9+
  11. 11.permissions: [
  12. 12..writeToPackageDirectory(
  13. 13.reason: "Generate source code from templates"
  14. 14.)
  15. 15.]
  16. 16.)
  17. 17.]
  18. 18.)
  19. 19.`
  20. 20.Use the correct plugin capability:
  21. 21.```swift
  22. 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. 1.Work around sandbox limitations with pre-defined output:
  2. 2.```swift
  3. 3.// Instead of writing to arbitrary locations, use pluginWorkDirectory
  4. 4.struct MyPlugin: BuildToolPlugin {
  5. 5.func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
  6. 6.let outputDir = context.pluginWorkDirectory
  7. 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. 1.Debug with sandbox disabled (development only):
  2. 2.```bash
  3. 3.# For command plugins
  4. 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 pluginWorkDirectory for 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.logger to debug file paths during plugin execution