<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Fskit on Lessons learned</title><link>https://eghuro.cz/tags/fskit/</link><description>Recent content in Fskit on Lessons learned</description><generator>Hugo</generator><language>en-us</language><copyright>© Alexandr Mansurov · Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)</copyright><lastBuildDate>Sat, 26 Sep 2026 16:30:00 +0200</lastBuildDate><atom:link href="https://eghuro.cz/tags/fskit/index.xml" rel="self" type="application/rss+xml"/><item><title>FSKit after mount -F: "ls: fts_read: Operation not permitted"</title><link>https://eghuro.cz/posts/fskit-mount-fts-read-operation-not-permitted/</link><pubDate>Sat, 26 Sep 2026 16:30:00 +0200</pubDate><guid>https://eghuro.cz/posts/fskit-mount-fts-read-operation-not-permitted/</guid><description>&lt;p&gt;My new FSKit filesystem mounted without complaint, but nothing inside it could be read:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -la ~/mnt/storage-test&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls: fts_read: Operation not permitted&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;total 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat ~/mnt/storage-test/sub/a.txt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat: /Users/me/mnt/storage-test/sub/a.txt: Operation not permitted&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;stat&lt;/code&gt; on the mount point worked. If your FSKit extension does the same after a &lt;code&gt;mount -F&lt;/code&gt; from the command line, check its sandbox before your code: the extension can see the source folder&amp;rsquo;s metadata but may not read its contents.&lt;/p&gt;&#10;&#10;&lt;h2 id="the-setup"&gt;The setup &lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;macOS 26, a file system extension built on FSKit&amp;rsquo;s &lt;code&gt;FSUnaryFileSystem&lt;/code&gt; that exposes a local folder through a mount point&lt;/li&gt;&#10;&lt;li&gt;A small host app, &lt;code&gt;StorageHost.app&lt;/code&gt;, in &lt;code&gt;/Applications&lt;/code&gt;; its executable runs &lt;code&gt;mount -F -t storagefs &amp;lt;source&amp;gt; &amp;lt;mount point&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;The extension &lt;code&gt;StorageFS.appex&lt;/code&gt; with the FSKit entitlement and the app sandbox&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Info.plist&lt;/code&gt; with &lt;code&gt;FSSupportsPathURLs&lt;/code&gt; set to true and &lt;code&gt;FSRequiresSecurityScopedPathURLResources&lt;/code&gt; set to false&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 id="what-went-wrong"&gt;What went wrong &lt;/h2&gt;&#10;&lt;p&gt;The first mount was over &lt;code&gt;~/Downloads&lt;/code&gt;, so the obvious suspect was privacy protection (TCC) for that folder. A fresh &lt;code&gt;~/storage-test&lt;/code&gt; with one file in it failed the same way, which ruled TCC out and pushed the search elsewhere.&lt;/p&gt;&#10;&lt;p&gt;My coding agent&amp;rsquo;s first log queries came back empty, and it told me there were no log entries. That was wrong. In zsh, &lt;code&gt;log&lt;/code&gt; is a builtin, so &lt;code&gt;log show ... 2&amp;gt;/dev/null&lt;/code&gt; runs the builtin, sends its &lt;code&gt;too many arguments&lt;/code&gt; error to &lt;code&gt;/dev/null&lt;/code&gt;, and prints nothing. Call &lt;code&gt;/usr/bin/log&lt;/code&gt; by its full path. With that, the kernel had the answer:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kernel (Sandbox) 3 duplicate reports for Sandbox: StorageFS(83505) deny(1) file-read-data /Users/me/storage-test&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The extension&amp;rsquo;s own log showed the failures from FSKit&amp;rsquo;s side: &lt;code&gt;readDirectory:...reply:error:1&lt;/code&gt; and &lt;code&gt;readFrom:...reply:error:1&lt;/code&gt;. Error 1 is &lt;code&gt;EPERM&lt;/code&gt;, which &lt;code&gt;ls&lt;/code&gt; turns into &lt;code&gt;fts_read: Operation not permitted&lt;/code&gt;.&lt;/p&gt;&#10;&#10;&lt;h2 id="fix"&gt;Fix &lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;mount -F&lt;/code&gt; gives the extension a plain path, not a security-scoped URL, so a sandboxed process can&amp;rsquo;t read an arbitrary path. For development, grant temporary sandbox exceptions in the extension&amp;rsquo;s &lt;code&gt;.entitlements&lt;/code&gt; file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.security.temporary-exception.files.home-relative-path.read-write&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.security.temporary-exception.files.absolute-path.read-write&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/Volumes/&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This covers your home folder and external drives. It doesn&amp;rsquo;t override privacy protection, so &lt;code&gt;~/Downloads&lt;/code&gt;, &lt;code&gt;~/Documents&lt;/code&gt;, and &lt;code&gt;~/Desktop&lt;/code&gt; may still be refused; test with a folder elsewhere. Temporary exceptions are for development on your own machine. A shipping extension should use security-scoped resources instead. &lt;code&gt;FSRequiresSecurityScopedPathURLResources&lt;/code&gt; in &lt;code&gt;Info.plist&lt;/code&gt; requests that; I haven&amp;rsquo;t managed a clean test yet (see below).&lt;/p&gt;&#10;&lt;p&gt;The same logs flagged two more problems that I fixed along the way:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-[FSModuleVolume(Project) getMaxFileSizeInBits]: Volume implements neither maxFileSizeInBits nor maxFileSize. One of them must be implemented.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-[FSVolumeConnector getStandardItemAttributesForItem:replyHandler:]_block_invoke: Reported attributes are incomplete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first needs &lt;code&gt;maximumFileSizeInBits&lt;/code&gt; on the volume. The second went away once the item attributes set &lt;code&gt;flags&lt;/code&gt;, which cleared the log entry.&lt;/p&gt;&#10;&#10;&lt;h2 id="reinstalling-switches-the-module-off"&gt;Reinstalling switches the module off &lt;/h2&gt;&#10;&lt;p&gt;To test each change, I replaced the app: &lt;code&gt;rm -rf /Applications/StorageHost.app&lt;/code&gt;, copied the new build, and launched it once. After that, &lt;code&gt;mount&lt;/code&gt; failed outright:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Module com.example.storage.fs is disabled!&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount: Unable to invoke task&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;pluginkit&lt;/code&gt; disagreed. &lt;code&gt;pluginkit -e use -i com.example.storage.fs&lt;/code&gt; succeeded, and &lt;code&gt;pluginkit -m&lt;/code&gt; listed the extension with a &lt;code&gt;+&lt;/code&gt;, which means enabled. FSKit still refused. It keeps its own switch in System Settings → General → Login Items &amp;amp; Extensions → File System Extensions, and I didn&amp;rsquo;t find a command-line way to flip it. After I turned StorageFS back on there, the next mount listed and read files, and appends, renames, truncation, and an 8 MB write-and-readback all worked.&lt;/p&gt;&#10;&lt;p&gt;The disabled module also spoiled my one test of security-scoped resources. With &lt;code&gt;FSRequiresSecurityScopedPathURLResources&lt;/code&gt; set to true, &lt;code&gt;mount&lt;/code&gt; failed with &lt;code&gt;Unable to invoke task&lt;/code&gt;, but the reinstall had just switched the module off, so the failure says nothing about the setting.&lt;/p&gt;&#10;&#10;&lt;h2 id="why"&gt;Why &lt;/h2&gt;&#10;&lt;p&gt;An FSKit module is an app extension, and FSKit requires it to run in the app sandbox. When an app hands a folder to the extension, sandbox access can travel with it as a security-scoped URL. &lt;code&gt;mount -F&lt;/code&gt; on the command line passes only a string. The sandbox lets the extension look up metadata for that path, which is why the mount and &lt;code&gt;stat&lt;/code&gt; worked, and denies &lt;code&gt;file-read-data&lt;/code&gt;, which every directory listing and file read needs.&lt;/p&gt;&#10;&#10;&lt;h2 id="takeaway"&gt;Takeaway &lt;/h2&gt;&#10;&lt;p&gt;If an FSKit mount works and every read returns &lt;code&gt;Operation not permitted&lt;/code&gt;, search the kernel log for &lt;code&gt;deny(1) file-read-data&lt;/code&gt; with &lt;code&gt;/usr/bin/log&lt;/code&gt;, then give the extension sandbox access to the source path. After every reinstall, check the File System Extensions switch before you trust a test result.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;ve got &lt;code&gt;FSRequiresSecurityScopedPathURLResources&lt;/code&gt; working with a command-line &lt;code&gt;mount -F&lt;/code&gt;, I&amp;rsquo;d like to hear how.&lt;/p&gt;&#10;</description></item></channel></rss>