Skip to main content
Question

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


Forum|alt.badge.img+4

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.

22 replies

_Daley
Forum|alt.badge.img+6
  • Contributor
  • 20 replies
  • January 17, 2025

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


AJPinto
Forum|alt.badge.img+26
  • Legendary Contributor
  • 2717 replies
  • January 17, 2025

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
Forum|alt.badge.img+25
  • Jamf Heroes
  • 3537 replies
  • January 17, 2025

@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. 


Samstar777
Forum|alt.badge.img+11
  • Valued Contributor
  • 134 replies
  • January 17, 2025

We have achieve this through our EDR Solution SentinelOne


Jason33
Forum|alt.badge.img+13
  • Honored Contributor
  • 223 replies
  • January 17, 2025

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


Forum|alt.badge.img+19
  • Honored Contributor
  • 582 replies
  • January 17, 2025

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. 


Jason33
Forum|alt.badge.img+13
  • Honored Contributor
  • 223 replies
  • January 17, 2025
Tribruin wrote:

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/diskmanagement.settings.yaml 


agungsujiwo
Forum|alt.badge.img+8
  • Contributor
  • 118 replies
  • January 22, 2025

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.


Forum|alt.badge.img+3
  • New Contributor
  • 7 replies
  • February 3, 2025
sdagley wrote:

@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. 


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


Forum|alt.badge.img+3
  • New Contributor
  • 7 replies
  • February 3, 2025
agungsujiwo wrote:

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. 


Forum|alt.badge.img
  • New Contributor
  • 1 reply
  • March 6, 2025

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


Forum|alt.badge.img+4

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
Forum|alt.badge.img+25
  • Jamf Heroes
  • 3537 replies
  • May 3, 2025
wewenttothemoon wrote:

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. 


@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.


Forum|alt.badge.img+4
sdagley wrote:

@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
Forum|alt.badge.img+25
  • Jamf Heroes
  • 3537 replies
  • May 3, 2025
wewenttothemoon wrote:

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! :)


@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.


Forum|alt.badge.img+4
sdagley wrote:

@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)



Forum|alt.badge.img+4
wewenttothemoon wrote:

@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
Forum|alt.badge.img+25
  • Jamf Heroes
  • 3537 replies
  • May 4, 2025
wewenttothemoon wrote:

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

@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.


Forum|alt.badge.img+4
sdagley wrote:

@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.


sdagley
Forum|alt.badge.img+25
  • Jamf Heroes
  • 3537 replies
  • May 9, 2025
wewenttothemoon wrote:

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


@wewenttothemoon I haven't had a chance to try it yet myself, but if you've got Jamf Pro 11.5.0 or later installed and have the Blueprints feature for DDM configurations enabled, and your environment is all macOS 15.0 or later, you should take a look the Storage Management restrictions Apple added: https://support.apple.com/guide/deployment/storage-management-declarative-configuration-dep2b9f009ed/web


Forum|alt.badge.img+4
sdagley wrote:

@wewenttothemoon I haven't had a chance to try it yet myself, but if you've got Jamf Pro 11.5.0 or later installed and have the Blueprints feature for DDM configurations enabled, and your environment is all macOS 15.0 or later, you should take a look the Storage Management restrictions Apple added: https://support.apple.com/guide/deployment/storage-management-declarative-configuration-dep2b9f009ed/web


@sdagley Yeah, so I've been looking at that but for some reason even though we meet the pre-reqs (Jamf Pro version 11.15.1, macOS 15.4.1), JAMF is not enabled DDM on any of our devices.. When I look through the server logs I noticed we have a ton of these errors:

2025-05-09 02:02:17,911 [ERROR] [-http-nio-2] [DssAssignmentErrorHandler] - Exception occurred during batch processing: 401 Unauthorized from POST https://dss.euc1.services.jamfcloud.com/api/v1/assignment/declaration/"obfuscated", Failed to associate 1 devices to DDS declaration: "obfuscated". Failed device management Ids: ["obfuscated"]

I don't know if this is related to why our cloud instance of jamf pro is not enabling DDM automatically. 

 


sdagley
Forum|alt.badge.img+25
  • Jamf Heroes
  • 3537 replies
  • May 9, 2025
wewenttothemoon wrote:

@sdagley Yeah, so I've been looking at that but for some reason even though we meet the pre-reqs (Jamf Pro version 11.15.1, macOS 15.4.1), JAMF is not enabled DDM on any of our devices.. When I look through the server logs I noticed we have a ton of these errors:

2025-05-09 02:02:17,911 [ERROR] [-http-nio-2] [DssAssignmentErrorHandler] - Exception occurred during batch processing: 401 Unauthorized from POST https://dss.euc1.services.jamfcloud.com/api/v1/assignment/declaration/"obfuscated", Failed to associate 1 devices to DDS declaration: "obfuscated". Failed device management Ids: ["obfuscated"]

I don't know if this is related to why our cloud instance of jamf pro is not enabling DDM automatically. 

 


@wewenttothemoon Unfortunately Blueprints is one of the new features that relies on enabling SSO for your Jamf Account. See the following for more info:

https://learn.jamf.com/en-US/bundle/jamf-account-documentation/page/Jamf_SSO_with_Jamf_Account.html

https://learn.jamf.com/en-US/bundle/technical-articles/page/Single_Sign-On_Options_for_Jamf_Pro_FAQ.html


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings