Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Grant a Support Tech Temporary Remote Mac Access

Short answer: add the technician to the admin group with dseditgroup, then write a one-shot launchd daemon that calls a de-escalation script at a fixed wall-clock time. The scripts below do both and the daemon unloads itself when it fires. On Figment, this is most natural on a physical plan (i3 at $49/month, M-series from $95/month, provisioned on request) where you fully control the admin account; the $29 virtual Intel macOS plan also runs dseditgroup and launchctl in its browser-console desktop terminal, so confirm the admin account and user-management permissions you need in the quote before you rely on this in production.

A support technician or external consultant usually needs real privilege — install a kext, clear a signing cache, repair a broken Xcode install — and the tempting path is to promote their account to admin and forget to demote it. That dormant admin account then sits on a machine that may also hold CI secrets, signing material, and customer credentials. Time-boxing the privilege to a fixed window closes that exposure with one script pair and about five minutes of setup.

Risks of a Permanent Admin Grant

macOS uses three account types, documented in Apple's user accounts guide: an administrator can add and manage other users, install apps and change system settings; a standard user can install apps and change only their own settings; a sharing-only user can reach shared files remotely but cannot log in interactively.

Leaving an admin account in place after a session creates two problems. The credentials persist on a machine you share with build pipelines and keystores. And the technician keeps the ability to modify CI configuration, Keychain items or network settings well after the ticket closes. Pin the privilege to a window instead and both exposures end with the clock.

Elevate an Existing Account vs. Create a Temporary One

OptionWhen to useRevert step
Elevate an existing standard accountTechnician already has a login you manageRemove admin group membership; account remains standard
Create a dedicated temporary accountNo pre-existing account; cleaner audit boundaryDelete the entire account at the end of the window

The scripts below cover the elevation case. To use a fresh account, prepend sudo sysadminctl -addUser techsupport -password '<temp>' -fullName "Support Technician" to escalate.sh and append sudo sysadminctl -deleteUser techsupport inside deescalate.sh.

Add and Remove Admin Membership with dseditgroup

macOS groups live in Directory Services. dseditgroup is the documented command-line tool to edit them:

# Add techsupport to the admin group
sudo dseditgroup -o edit -a techsupport -t user admin

# Remove techsupport from the admin group
sudo dseditgroup -o edit -d techsupport -t user admin

# Check membership (exit 0 = member, exit 64 = not a member)
dseditgroup -o checkmember -m techsupport admin

Scripted confirmation uses the exit code from checkmember rather than parsing stdout.

The Script Pair: escalate.sh and deescalate.sh

Save both to /usr/local/bin/ owned by root with chmod 700 so an unprivileged user cannot modify them.

/usr/local/bin/escalate.sh

#!/bin/bash
# escalate.sh — add a user to admin and register a timed de-escalation
# Usage: sudo escalate.sh <username> <minutes>
# Example: sudo escalate.sh techsupport 120

set -euo pipefail

TECH_USER="${1:?Usage: escalate.sh <username> <minutes>}"
MINUTES="${2:?Usage: escalate.sh <username> <minutes>}"
AUDIT_LOG="/var/log/admin_escalation.log"
PLIST_LABEL="com.company.deescalate.${TECH_USER}"
PLIST_PATH="/Library/LaunchDaemons/${PLIST_LABEL}.plist"

# Confirm the user exists before touching groups
if ! id "${TECH_USER}" &>/dev/null; then
  echo "ERROR: user '${TECH_USER}' does not exist." >&2
  exit 1
fi

# Compute the de-escalation wall-clock time
DEESC_EPOCH=$(( $(date +%s) + MINUTES * 60 ))
DEESC_HOUR=$(date -r "${DEESC_EPOCH}" '+%-H')
DEESC_MIN=$(date -r  "${DEESC_EPOCH}" '+%-M')

dseditgroup -o edit -a "${TECH_USER}" -t user admin
printf '%s ESCALATE user=%s until=%s\n' \
  "$(date '+%Y-%m-%dT%H:%M:%S%z')" \
  "${TECH_USER}" \
  "$(date -r "${DEESC_EPOCH}" '+%Y-%m-%dT%H:%M:%S%z')" \
  >> "${AUDIT_LOG}"

cat > "${PLIST_PATH}" <<PLIST
<?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>Label</key>
  <string>${PLIST_LABEL}</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/deescalate.sh</string>
    <string>${TECH_USER}</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>${DEESC_HOUR}</integer>
    <key>Minute</key>
    <integer>${DEESC_MIN}</integer>
  </dict>
  <key>RunAtLoad</key>
  <false/>
</dict>
</plist>
PLIST

chown root:wheel "${PLIST_PATH}"
chmod 644 "${PLIST_PATH}"

# On macOS 13 (Ventura) and later, prefer: launchctl bootstrap system "${PLIST_PATH}"
launchctl load "${PLIST_PATH}"

echo "Escalation active. Auto-revocation scheduled at $(date -r "${DEESC_EPOCH}")."
echo "Plist: ${PLIST_PATH}"

/usr/local/bin/deescalate.sh

#!/bin/bash
# deescalate.sh — remove admin, clean up the plist, write an audit line
# Usually called by launchd; can also be run manually: sudo deescalate.sh <user>

set -euo pipefail

TECH_USER="${1:?Usage: deescalate.sh <username>}"
AUDIT_LOG="/var/log/admin_escalation.log"
PLIST_LABEL="com.company.deescalate.${TECH_USER}"
PLIST_PATH="/Library/LaunchDaemons/${PLIST_LABEL}.plist"

dseditgroup -o edit -d "${TECH_USER}" -t user admin 2>/dev/null || true

if dseditgroup -o checkmember -m "${TECH_USER}" admin &>/dev/null; then
  printf '%s DEESCALATE_FAILED user=%s\n' \
    "$(date '+%Y-%m-%dT%H:%M:%S%z')" "${TECH_USER}" >> "${AUDIT_LOG}"
  exit 1
fi

printf '%s DEESCALATED user=%s\n' \
  "$(date '+%Y-%m-%dT%H:%M:%S%z')" "${TECH_USER}" >> "${AUDIT_LOG}"

if [ -f "${PLIST_PATH}" ]; then
  launchctl unload "${PLIST_PATH}" 2>/dev/null || true
  rm -f "${PLIST_PATH}"
fi

echo "Admin access removed for ${TECH_USER}."

These are example scripts you should run through your own review and test on a non-production account before using in support rotations.

Scheduling the Auto-Revocation with launchd

The plist uses a StartCalendarInterval dict with only Hour and Minute. Per Apple's launchd job guide, any missing calendar key behaves as a wildcard (the cron model), so a plist with just Hour and Minute fires at that time every day. That is fine here because deescalate.sh deletes the plist on the first run, so there is no second-day execution.

Two limitations to keep in mind. launchd only fires when the system is awake: if the Mac is asleep or powered off at the scheduled time, the job runs at the next wake past the trigger. Rental Macs usually stay on, but confirm your power settings before you depend on this. And the window is an upper bound on exposure, not a promise about what happened inside it — keep an audit log and backups regardless.

Logging and Confirming the Window

Both scripts append single-line records to /var/log/admin_escalation.log. After a session, confirm the lifecycle:

sudo grep "techsupport" /var/log/admin_escalation.log
# Example output:
# 2026-11-10T14:00:00+0000 ESCALATE user=techsupport until=2026-11-10T16:00:00+0000
# 2026-11-10T16:00:03+0000 DEESCALATED user=techsupport

The gap between ESCALATE and DEESCALATED is the real exposure window. /var/log/ is root-writable only, so a standard user cannot tamper with the record. Rotate the file with newsyslog if the machine stays up for months.

Optional: Notify the Technician on Expiry

Append this before the final echo in deescalate.sh:

if pgrep -q -u "${TECH_USER}" Finder 2>/dev/null; then
  sudo -u "${TECH_USER}" osascript -e \
    "display notification \"Your admin access has been removed.\" with title \"Session Expired\""
fi

It only fires if the technician is still logged in, and on a VNC session the banner shows in the remote display.

Choosing the Right Figment Plan for This Workflow

If your support rotation is occasional and the technician mostly needs to inspect logs or re-run a build, the $29 virtual Intel macOS plan runs dseditgroup, launchctl and the audit log through its browser-console desktop terminal — inbound SSH is not currently offered, so operate the escalation from the console rather than a shell tunnel. If the technician needs to drive Xcode, sign for a physical Mac target, or test ARM-only binaries, choose the physical i3 at $49/month or M-series from $95/month and ask for the exact admin and remote-access configuration in the quote; physical hardware specs are confirmed when Figment confirms supply. Managed Build Mac at $249/month adds one M-series Mac with CI setup, backups, monitoring and 30 minutes of monthly support if you want the support window and the CI machine under one plan.

Start a configuration request at /mac-hosting/start, or compare plans at /mac-hosting.

Related reading:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms