@@ -116,16 +116,19 @@ public actor SecretsMountManager {
116116 }
117117
118118/// Build mount options based on configuration
119- public nonisolated func buildMountOptions( config: XAppleSecretsConfig ) -> [ String ] {
120- var options : [ String ] = [ ]
121- #if os(Linux)
122- options. append ( " size=1m " )
123- #endif
124- options. append ( " mode=0400 " )
125- if config. noexec { options. append ( " noexec " ) }
126- if config. nosuid { options. append ( " nosuid " ) }
127- return options
128- }
119+ public nonisolated func buildMountOptions( config: XAppleSecretsConfig ) -> [ String ] {
120+ var options : [ String ] = [ ]
121+ #if os(Linux)
122+ options. append ( " size=1m " )
123+ options. append ( " mode=0400 " )
124+ #else
125+ // macOS: tmpfs doesn't support -o mode. Permissions set via chmod after mount.
126+ // size is also not supported on macOS tmpfs
127+ #endif
128+ if config. noexec { options. append ( " noexec " ) }
129+ if config. nosuid { options. append ( " nosuid " ) }
130+ return options
131+ }
129132
130133 /// Load secrets from enclave with optional filtering
131134 public func loadSecrets( filter: [ String ] ? ) async throws -> [ String : String ] {
@@ -161,27 +164,76 @@ public actor SecretsMountManager {
161164 return result
162165 }
163166
164- /// Mount tmpfs at the specified path
165- private func mountTmpfs( at path: String , options: [ String ] ) async throws {
166- let optionsString = options. joined ( separator: " , " )
167- let task = Process ( )
168- task. launchPath = " /sbin/mount "
169- task. arguments = [ " -t " , " tmpfs " , " -o " , optionsString, " tmpfs " , path]
170-
171- let pipe = Pipe ( )
172- task. standardOutput = pipe
173- task. standardError = pipe
174-
175- try task. run ( )
176- task. waitUntilExit ( )
167+ /// Mount tmpfs at the specified path
168+ private func mountTmpfs( at path: String , options: [ String ] ) async throws {
169+ #if os(macOS)
170+ // macOS: Use mount_tmpfs directly with its specific syntax
171+ // usage: mount_tmpfs [-o options] [-i | -e] [-n max_nodes] [-s max_mem_size] <directory>
172+ // Note: macOS mount_tmpfs is restricted to root/superuser in SIP-enabled systems
173+ let task = Process ( )
174+ task. launchPath = " /sbin/mount_tmpfs "
175+ // Convert our options format to mount_tmpfs format
176+ var args : [ String ] = [ ]
177+ // Filter out unsupported options - macOS tmpfs doesn't support mode/size via -o
178+ let supportedOptions = options. filter { !$0. hasPrefix ( " mode= " ) && !$0. hasPrefix ( " size= " ) }
179+ if !supportedOptions. isEmpty {
180+ args. append ( " -o " )
181+ args. append ( supportedOptions. joined ( separator: " , " ) )
182+ }
183+ args. append ( path)
184+ task. arguments = args
185+
186+ let pipe = Pipe ( )
187+ task. standardOutput = pipe
188+ task. standardError = pipe
189+
190+ try task. run ( )
191+ task. waitUntilExit ( )
192+
193+ guard task. terminationStatus == 0 else {
194+ let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
195+ let errorMessage = String ( data: data, encoding: . utf8) ?? " Unknown error "
196+ // Log warning but don't fail - tmpfs may be restricted on macOS
197+ logger. warning ( " Failed to mount tmpfs (expected on macOS without root): \( errorMessage) " )
198+ // On macOS without root, create a regular directory as fallback
199+ try ? FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: true )
200+ // Set restrictive permissions
201+ let chmodTask = Process ( )
202+ chmodTask. launchPath = " /bin/chmod "
203+ chmodTask. arguments = [ " 700 " , path]
204+ try ? chmodTask. run ( )
205+ chmodTask. waitUntilExit ( )
206+ return
207+ }
177208
178- guard task. terminationStatus == 0 else {
179- let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
180- let errorMessage = String ( data: data, encoding: . utf8) ?? " Unknown error "
181- logger. error ( " Failed to mount tmpfs: \( errorMessage) " )
182- throw SecretsError . mountFailed ( underlying: NSError ( domain: " MountError " , code: Int ( task. terminationStatus) , userInfo: [ NSLocalizedDescriptionKey: errorMessage] ) )
183- }
184- }
209+ // Set restrictive permissions after mount
210+ let chmodTask = Process ( )
211+ chmodTask. launchPath = " /bin/chmod "
212+ chmodTask. arguments = [ " 700 " , path]
213+ try ? chmodTask. run ( )
214+ chmodTask. waitUntilExit ( )
215+ #else
216+ // Linux: tmpfs supports -o options
217+ let optionsString = options. joined ( separator: " , " )
218+ let task = Process ( )
219+ task. launchPath = " /sbin/mount "
220+ task. arguments = [ " -t " , " tmpfs " , " -o " , optionsString, " tmpfs " , path]
221+
222+ let pipe = Pipe ( )
223+ task. standardOutput = pipe
224+ task. standardError = pipe
225+
226+ try task. run ( )
227+ task. waitUntilExit ( )
228+
229+ guard task. terminationStatus == 0 else {
230+ let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
231+ let errorMessage = String ( data: data, encoding: . utf8) ?? " Unknown error "
232+ logger. error ( " Failed to mount tmpfs: \( errorMessage) " )
233+ throw SecretsError . mountFailed ( underlying: NSError ( domain: " MountError " , code: Int ( task. terminationStatus) , userInfo: [ NSLocalizedDescriptionKey: errorMessage] ) )
234+ }
235+ #endif
236+ }
185237
186238 /// Unmount tmpfs at the specified path
187239 private func unmountTmpfs( at path: String ) async throws {
0 commit comments