How to Block External USB-C Access for All Mac Devices via Jamf Pro?

deep786
New Contributor II

We want to block external USB-C access on all our Mac devices using Jamf Pro. While we are aware that Jamf Protect offers functionality to block USB access, we do not currently have Protect and would like to achieve this using Jamf Pro alone.

I attempted to configure restrictions in a Configuration Profile under Media (even though it is marked as deprecated). This approach worked on Intel-based Macs running macOS 15.2 (Sequoia), but it does not seem to work on Apple Silicon devices.

Is there a way to consistently block USB access across all Mac devices (both Intel and Apple Silicon) using Jamf Pro?

Any advice or guidance would be greatly appreciated.

19 REPLIES 19

_Daley
New Contributor III

Unfortunately there is no way to do this without as you mentioned Jamf Protect, or a similar piece of software. 

AJPinto
Esteemed Contributor

Apple moved this functionality from the MDM framework to the Security framework a few years ago. If you check the restrictions payload for a configuration profile you still see check boxes for this stuff with deprecated next to it.

There is nothing Jamf Pro can do in this space; you need to get the right tool for the job. DLP is not cheap, and your employer needs to be prepared to pay for it.

sdagley
Esteemed Contributor III

@deep786 Are you talking about USB devices in general, or specifically USB external storage devices? If the latter, would forcing any external physical storage device into Read-Only mode be a viable option? If it is you can achieve that using Jamf Pro to install a Launch Daemon and a script. In simplest terms the Launch Daemon would be configured to trigger whenever a drive was mounted and it would run a script to force any external physical storage devices into Read-Only mode. 

K_SB
New Contributor

@sdagley can you please go over each step on how to configure this method you are referring to ?

Samstar777
Contributor III

We have achieve this through our EDR Solution SentinelOne

Jason33
Contributor III

I thought I read somewhere that declarations were being implemented sometime soon for managing USB devices again?

Tribruin
Valued Contributor II

Apple did add media blocking to DDM in macOS Sequoia. Jamf announced support for this feature at JNUC last October through Blueprints, but has not released it yet. Hopefully in the next few months. 

Ah yes, thats right. I found the declaration that I was thinking of.

https://github.com/apple/device-management/blob/release/declarative/declarations/configurations/disk... 

agungsujiwo
Contributor II

Hi @deep786 

Here is the script for the Mac Apple Silicon Device :
Testing on Mac M1 OS Sonoma works 
Testing External Disk : Flashdisk Sandisk , SSD External ADATA

Script Name : AutoEjectDiskExternal.sh
Its function is every time an external disk is plugged into the Mac it will be ejected.

#!/bin/bash
#CreateBy Agung sujiwo 22/01/2025
#AutoEjectDiskExternal.sh
#Function to eject a specific disk

#Fuction eject disk.
eject_disk() {
    local disk=$1
    echo "Ejecting $disk..."
    diskutil eject "$disk" >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo "Successfully ejected $disk."
    else
        echo "Failed to eject $disk. You might need admin privileges."
    fi
}

# Main loop to monitor USB drives every 5 Seconds
while true; do
    # Get a list of all external disks (exclude internal drives)
    connected_disks=$(diskutil list | grep external | awk '{print $1}')
    
    if [ ! -z "$connected_disks" ]; then
        echo "Detected external disk(s):"
        echo "$connected_disks"
        
        # Eject each detected external disk
        for disk in $connected_disks; do
            eject_disk "$disk"
        done
    fi

    # Sleep for 5 seconds before re-checking
    sleep 5
done

 

Here is the script to kill the running AutoEjectDiskExternal.sh script
Script Name : ForceQuitAutoEjectDiskExternal.sh

#!/bin/bash
#Create by Agung sujiwo 22/1/2025
#ForceQuitAutoEjectDiskExternal.sh
#Kill Script AutoEjectDiskExternal.sh

pkill -f AutoEjectDiskExternal.sh

In the next step, you can add this script, create a policy with the 'Login' trigger, and assign the appropriate scope. If you have an alternative approach or method, feel free to implement it.

@agungsujiwo this is very helpful. Can you please also show how we can configure a separate policy that only allows external USB storage devices as Read Only mode? @sdagley was referring to something like this where you would configure a Launch Daemon and a script within Jamf Pro and only allow external USB storage devices to Read Only mode. 

i_user
New Contributor

I'm also interested on the method how to distribute the scripts. Using Launch Agent etc. can anyone help here?

wewenttothemoon
New Contributor

Yes, also looking for the same need to restrict access to all external storage devices. The script above works, but the problem is the policy just runs and blocks other policies from processing until the script is killed. 

sdagley
Esteemed Contributor III

@wewenttothemoon You wouldn't run a script like that from a Jamf Pro policy, it needs to be running via a LaunchDaemon. And rather than running a script that's continually checking to see what external drives are connected IMO the LaunchDaemon should trigger when a volume is mounted.

Yeah, that makes sense. I'm a rather new mac admin so I've not yet gone down the rabbit hole of how to create a LaunchDaemon. If you have any script/logic you can share especially for it to trigger when a volume is mounted, it would be immensely appreciated. Thanks for your feedback! :)

sdagley
Esteemed Contributor III

@wewenttothemoon I can't share the script/LaunchDaemon based tool I wrote that blocks write access to any external drive that isn't APFS Encrypted, but I can refer you to the  https://github.com/txhaflaire/DiskEncrypter project on GitHub which demonstrates the technique of checking volumes as they are mounted.

You might also consider looking a DLP product (I can't name specific ones) for your Macs that will provide restrictions on what can be written to an external drive.

@sdagley thank you for sharing that resource, it was very helpful! We are using MS Defender ATP so I will ask the respective team what the options are for macOS. 

In the meantime I have created the following .sh and deployed as a .pkg with a plist(LaunchDaemon) and postinstall script to load it. Let me know if you see any glaring issues, please!

script:

## Define variables
appname="AutoEjectDiskExternal"
logandmetadir="/Library/Logs/JAMF/Scripts/$appname"
log="$logandmetadir/$appname.log"

## Check if the log directory has been created
if [ -d "$logandmetadir" ]; then
    echo "# $(date) | Log directory already exists - $logandmetadir"
else
    echo "# $(date) | Creating log directory - $logandmetadir"
    mkdir -p "$logandmetadir"
fi

# Start logging
exec 1>> "$log" 2>&1

echo ""
echo "############################################################"
echo "# $(date) | Starting AutoEjectExternalDisk script"
echo "############################################################"
echo ""

#Fuction eject disk.
eject_disk() {
    local disk=$1
    echo " $(date) | Ejecting $disk..."
    diskutil eject "$disk" >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo " $(date) | Successfully ejected $disk."
    else
        echo " $(date) | Failed to eject $disk. You might need admin privileges."
    fi
}

# Main loop to monitor USB drives every 5 Seconds
while true; do
    # Get a list of all external disks (exclude internal drives)
    connected_disks=$(diskutil list external physical | grep "/dev/disk" | awk '{print $1}')
    
    if [ ! -z "$connected_disks" ]; then
        echo " $(date) | Detected external disk(s):"
        echo " $(date) | $connected_disks"
        
        # Eject each detected external disk
        for disk in $connected_disks; do
            eject_disk "$disk"
        done
    fi

    # Sleep for 5 seconds before re-checking
    sleep 10

#echo ""
#echo "############################################################"
#echo "# $(date) | Script End"
#echo "############################################################"
#echo ""

done

 

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>com.org.volumewatcher</string>
	<key>OnDemand</key>
	<true/>
	<key>ProgramArguments</key>
	<array>
		<string>/bin/sh</string>
		<string>/Library/Application Support/Custom/AutoEjectExternalDisk.sh</string>
	</array>
	<key>StartOnMount</key>
	<true/>
</dict>
</plist>

 

postinstall:

#!/bin/sh
## postinstall
pathToScript=$0
pathToPackage=$1
targetLocation=$2
targetVolume=$3

# Set correct permissions
chmod 755 "/Library/Application Support/Custom/AutoEjectExternalDisk.sh"
chown root:wheel "/Library/Application Support/Custom/AutoEjectExternalDisk.sh"
 
chmod 644 /Library/LaunchDaemons/com.org.volumewatcher.plist
chown root:wheel /Library/LaunchDaemons/com.org.volumewatcher.plist
 
# Load LaunchDaemon safely
/bin/launchctl load /Library/LaunchDaemons/com.org.volumewatcher.plist || echo "Failed to load LaunchDaemon, manual load may be required."
 
 
exit 0		## Success
#exit 1		## Failure

 

I created the package using JAMF Composer. If you run into issues with the script or postinstall it could be windows EOL (notepad++ can automatically convert EOL to unix), or the script must be executable chmod +x /path/to/script.sh (or it can have a quarantine flag)


Correction I made to the script so it does not loop and only runs when a drive is mounted:


#!/bin/bash

## Define variables
appname="AutoEjectDiskExternal"
logandmetadir="/Library/Logs/JAMF/Scripts/$appname"
log="$logandmetadir/$appname.log"

## Check if the log directory has been created
if [ -d "$logandmetadir" ]; then
    echo "# $(date) | Log directory already exists - $logandmetadir"
else
    echo "# $(date) | Creating log directory - $logandmetadir"
    mkdir -p "$logandmetadir"
fi

# Start logging
exec 1>> "$log" 2>&1

echo ""
echo "############################################################"
echo "# $(date) | USB or External Drive Detected : Starting AutoEjectExternalDisk script"
echo "############################################################"
echo ""

#Fuction eject disk.
eject_disk() {
    local disk=$1
    echo " $(date) | Ejecting $disk..."
    diskutil eject "$disk" >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo " $(date) | Successfully ejected $disk."
    else
        echo " $(date) | Failed to eject $disk. You might need admin privileges."
    fi
}

# Main process to eject USB Drives

    # Get a list of all external disks (exclude internal drives)
    connected_disks=$(diskutil list external physical | grep "/dev/disk" | awk '{print $1}')
    
    if [ ! -z "$connected_disks" ]; then
        echo " $(date) | Detected external disk(s): $connected_disks"
        
        # Eject each detected external disk
        for disk in $connected_disks; do
            eject_disk "$disk"
        done
    else
	echo " $(date) | No external disks detected."
    fi

echo ""
echo "############################################################"
echo "# $(date) | USB or External Drive unmounted : Script End"
echo "############################################################"
echo ""

exit 0

sdagley
Esteemed Contributor III

@wewenttothemoon Just a few suggestions:

Add a RunAtLoad key with a value of true to your LaunchDaemon .plist so the script will run when installed to check for any mounted volumes.

The OnDemand key is deprecated and should not be used any more (See https://www.launchd.info/ for a guide to LaunchDaemons/LaunchAgents in general).

Adding the force option to your diskutil eject command might make it more effective.

@sdagley Much appreciated! Changes have been implemented and tested. Thanks again for the help and references.