The Mac app closed on connect, and our own Mac could not show why

7 min read | KEIBIDROP Series | October 2026

By Marius-Florin Cristian

A user reported that the Mac version of KEIBIDROP closed every time they connected in Teleport mode, the mode that shows a friend's folder as a folder on your own computer. The app's signature lacked one entitlement: Apple's hardened runtime refuses to load a library signed by another developer unless the app declares the exception, and macFUSE is signed by its own author. Our development Mac has System Integrity Protection off, which turns that check off, so every signed release since May passed our tests and failed on every normal Mac. The fix is one line in the signing step. This post walks through how the log, a truncated crash report, a 15-line probe and one kernel message found it.

Teleport mode on a Mac needs macFUSE

KEIBIDROP connects two computers over the internet. In normal mode you send and receive files. In Teleport mode the other computer's folder appears as a folder on yours, and files download as applications open them. On a Mac that folder is a FUSE file system, and the driver for it is macFUSE, a separate install from its own author, Benjamin Fleischer. Our app loads macFUSE's library, libfuse.2.dylib, at the moment it mounts the folder, through a Go library called cgofuse.

The report said normal mode was fine

The report came through the app's own Report problem form: the app keeps closing on connect in Teleport mode, normal mode is fine. The attached log showed six Teleport connects on one Mac. In every one, the last line was the call to mount the folder. Between 30 milliseconds and one second later the log stopped, and the next line was the app starting again. The six connects in normal mode on the same Mac ran for hours.

The same user had also pasted the macOS crash report into the form. It said the process died with SIGABRT on a background thread, and it listed thread 0, the one drawing the window. Then it stopped, because our server cut the message at 4,000 characters. The crashed thread's stack was in the part we cut.

A Go panic inside a library ends the whole app with SIGABRT

The desktop app is a Rust program with the Go engine linked in as a static library. When Go code in a normal Go program panics and nothing recovers it, the program prints the panic and exits. When the same happens inside a Go library linked into another program, the Go runtime raises SIGABRT instead, so the host program gets a crash report. That matched the report. Apple's crash reporter cannot walk Go stacks, so on a Go thread it shows only the two runtime frames that raise the signal, which is why even a complete report would have named the thread and not the function.

cgofuse has one panic on the mount path: when it cannot load the FUSE library it does panic("cgofuse: cannot find FUSE"). Our code did not recover that panic. The question became why dlopen of a library that was installed would fail on that Mac and not on ours.

The hardened runtime refuses a library signed by another developer

Apps distributed outside the App Store are notarized by Apple, and notarization requires the hardened runtime, a set of restrictions the app opts into when it is signed with codesign --options runtime. One of them is library validation: the process may load code signed by Apple or by the same Team ID as the app, and nothing else. Any exception has to be declared in an entitlements file at signing time. Our signing step had no entitlements file at all.

macFUSE's library is signed by its author's Team ID, 3T5GSNBU6W. Our app is signed by ours, 74WXJSBRUQ. Under library validation that dlopen is refused, cgofuse gets a null handle, panics, and the app dies. The exception that permits it is com.apple.security.cs.disable-library-validation, and the notarization service accepts it; every app that loads macFUSE ships with it.

SIP off on the development Mac hid it since May

System Integrity Protection, SIP, is the macOS setting that stops even the administrator from changing system files, loading unapproved kernel code, or attaching debuggers to Apple's processes. Developers turn it off from the recovery system when they need those things, and ours was off. With SIP off the kernel also stops enforcing library validation on hardened-runtime processes. The user's crash report says "System Integrity Protection: enabled", which is the default on every Mac Apple ships.

A 15-line C program made the difference visible on our own machine. It opens the library and prints the error:

#include <dlfcn.h>
#include <stdio.h>
int main(void) {
    void *h = dlopen("/usr/local/lib/libfuse.2.dylib", RTLD_NOW);
    if (!h) { printf("dlopen failed: %s\n", dlerror()); return 1; }
    printf("dlopen ok\n");
    return 0;
}

Signed three ways on the SIP-off Mac:

SignatureResult on a Mac with SIP off
codesign -s - probe (ad hoc)dlopen ok
codesign -s - --options runtime probe (hardened runtime, how we ship)dlopen ok, because SIP off turns the check off
codesign -s - --options runtime,library probe (validation forced on)mapping process and mapped file (non-platform) have different Team IDs

The third line is the failure every normal Mac produces from the second signature. The flag library forces the check that SIP off had disabled, so a development Mac with SIP off can still test what users get.

A Mac with SIP on reproduced it, and the kernel named it

On a second Mac with SIP on, the released 0.5.0 build died the same way, 737 milliseconds after the mount call. Its crash report listed 276 loaded libraries and libfuse.2.dylib was not among them, so the library had never been mapped. The kernel log had the reason, with the process id from the crash report:

$ log show --last 15m --predicate 'eventMessage CONTAINS "Library Validation failed"'
kernel: (AppleMobileFileIntegrity) Library Validation failed: Rejecting
'/usr/local/lib/libfuse.2.dylib' (Team ID: 3T5GSNBU6W, platform: no)
for process 'keibidrop(13252)' (Team ID: 74WXJSBRUQ, platform: no),
reason: mapping process and mapped file (non-platform) have different Team IDs

That line is 14 milliseconds after our log's mount call. The same 0.5.0 app bundle, re-signed with the entitlement and nothing else changed, loaded the library on that Mac and went on to macFUSE's own dialog asking to allow the extension. After the restart it mounted.

The fix is one entitlement, and a recover as the safety net

The entitlements file is short:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.security.cs.disable-library-validation</key>
  <true/>
</dict>
</plist>

Every codesign call in the build now passes --entitlements packaging/macos/KeibiDrop.entitlements. Notarization accepted the 0.5.1 builds, and Gatekeeper on the second Mac reports them as "Notarized Developer ID".

The second change is for the next time a library cannot load for any reason. The mount call now runs under a recover, so cgofuse's panic becomes an error, the session continues without the folder, and the notification says in plain words what to do: open System Settings, allow macFUSE, restart. The old text told Mac users to edit /etc/fuse.conf, which is a Linux file.

// hostMount runs mount, which blocks for the life of the host, and returns a
// panic from it as an error.
func hostMount(mount func() bool) (ok bool, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("%v", r)
        }
    }()
    return mount(), nil
}

Report problem now keeps a crash report whole

The 4,000-character cap on the message, in the app and on the server, is now 256 KB. A macOS crash report with all threads and the list of loaded libraries is about 50 KB; the second Mac's was 49,700 characters. The attached log was never the problem: it keeps the newest 4 MB of text, and the user's 140 KB log arrived complete.

Limits. Both failing Macs are Apple silicon and the probe ran on an Intel Mac with SIP off, so the failing build was not run on an Intel Mac with SIP on. macFUSE came from its own installer with its kernel extension; FUSE-T and macFUSE's FSKit mode were not tested. The 14 and 737 millisecond figures are one connect each on one machine.

Related: two peers editing one folder with no locks, git clone between two peers through a mounted folder, and the release note on keibidrop.com.