Removable storage devices like USB drives are indispensable tools for data transfer, backups, and system recovery. However, they also pose a serious security risk if left unmanaged—particularly in enterprise or MSP environments. Knowing whether USB storage access is enabled on your Linux systems is not just a matter of convenience; it’s a matter of policy enforcement, security compliance, and operational awareness. This post explores a shell script that automates the process of checking USB storage status in Linux and optionally integrates with NinjaOne to report findings.
Background
In Linux, control over USB storage access is primarily handled by kernel modules—specifically, the usb_storage and uas (USB Attached SCSI) drivers. System administrators can disable these drivers either by blacklisting them or by configuring them to execute harmless commands instead of loading.
The script reviewed here is built to inspect the /etc/modprobe.d directory, where such module-loading rules typically reside. It’s especially relevant in environments managed through NinjaOne, where centralized oversight and automated compliance checks are essential. Managed Service Providers (MSPs) and internal IT teams benefit from this kind of tooling to ensure that endpoints conform to corporate policies.
The Script:
#!/usr/bin/env bash # Description: Checks if the linux machine currently allows access to USB storage devices and optionally saves the results to a custom field. # By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. # Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. # Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. # Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. # Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. # Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. # Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. # EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). # # The script checks the modprobe.d folder for any files that may block the USB storage driver or the USB Attached SCSI driver. # There are two methods of loading USB storage drivers that allow mounting of USB storage devices: # 1. Implicit loading: The USB storage driver is loaded automatically when a USB storage device is connected. # 2. Explicit loading: The USB storage driver is loaded manually by a user, program, or script. # # Release Notes: Initial Release # # Preset Parameter: -CustomField "usbStorageStatus" # The custom field to save the USB storage status to. # # Preset Parameter: --help # Displays the help menu. # # Functions # Print an error message and exit with a specific status code die() { local _ret="${2:-1}" test "${_PRINT_HELP:-no}" = yes && print_help >&2 echo "$1" >&2 exit "${_ret}" } print_help() { printf '\n\n%s\n\n' 'Usage: [-CustomField "custom_field_name"]' printf ' %s\n' '-CustomField' printf ' The custom field to save the USB storage status to.\n' } # Parse the command-line arguments parse_commandline() { while test $# -gt 0; do _key="$1" case "$_key" in -CustomField | --CustomField | -customfield | --customfield | -cf | --cf) _arg_customfield="CustomField" ;; --help | -help | -h | --h) _PRINT_HELP=yes die "" 0 ;; *) _PRINT_HELP=yes die "[Error] Got an unexpected argument '$1'" 1 ;; esac shift done } function SetCustomField() { if [[ -z "$1" ]] || [[ -z "$2" ]]; then echo "[Error] Missing required arguments." return 1 fi local customfieldName=$1 local customfieldValue=$2 if [ -f "${NINJA_DATA_PATH}/ninjarmm-cli" ]; then if [ -x "${NINJA_DATA_PATH}/ninjarmm-cli" ]; then if "$NINJA_DATA_PATH"/ninjarmm-cli get "$customfieldName" >/dev/null; then # check if the value is greater than 10000 characters if [ ${#customfieldValue} -gt 10000 ]; then echo "[Warn] Custom field value is greater than 10000 characters" >&2 return 1 fi if ! echo "${customfieldValue::10000}" | "$NINJA_DATA_PATH"/ninjarmm-cli set --stdin "$customfieldName"; then echo "[Warn] Failed to set custom field" >&2 return 1 else echo "[Info] Custom field value set successfully" fi else echo "[Warn] Custom Field ($customfieldName) does not exist or agent does not have permission to access it" >&2 return 1 fi else echo "[Warn] ninjarmm-cli is not executable" >&2 return 1 fi else echo "[Warn] ninjarmm-cli does not exist" >&2 return 1 fi } parse_commandline "$@" # If script form variables are used, replace the command line parameters with their value. if [[ -n "${customField}" ]]; then _arg_customfield="${customField}" fi # Variables # Modprobe.d folder modprobFolder="/etc/modprobe.d" # Check if we have read access to the modprobe.d folder if ! [ -r "${modprobFolder}" ]; then if [[ -n "${_arg_customfield}" ]]; then SetCustomField "${_arg_customfield}" "Unable to Determine" fi die "[Error] User ${USER} has no read access to modprobe.d folder" 1 fi # Check if modprobe.d folder does not exist if ! [ -d "${modprobFolder}" ]; then if [[ -n "${_arg_customfield}" ]]; then SetCustomField "${_arg_customfield}" "Unable to Determine" fi die "[Error] Modprobe folder does not exist" 1 fi # Set default values UsbStorage="Enabled" usb_storage_status="Enabled" uas_status="Enabled" usb_storage_driver_status="Enabled" uas_driver_status="Enabled" # Check if usb_storage or uas is blocked in other files for file in "${modprobFolder}"/*; do if ! [ -r "${file}" ]; then echo "[Warn] User ${USER} has no read access to ${file}" fi # Check if implicit loading is blocked if grep -q "blacklist usb_storage" "${file}"; then echo "[Info] USB storage driver is blocked in ${file}" usb_storage_status="Disabled" fi # Check if implicit loading is blocked if grep -q "blacklist uas" "${file}"; then echo "[Info] USB Attached SCSI driver is blocked in ${file}" uas_status="Disabled" fi # Check if explicit loading is blocked if grep -q "install usb-storage /bin/true" "${file}"; then echo "[Info] USB storage driver is blocked in ${file}" usb_storage_driver_status="Disabled" fi # Check if explicit loading is blocked if grep -q "install uas /bin/true" "${file}"; then echo "[Info] USB Attached SCSI driver is blocked in ${file}" uas_driver_status="Disabled" fi done # Output the status of implicit loading if [ "${usb_storage_status}" == "Disabled" ] && [ "${uas_status}" == "Disabled" ]; then echo "[Info] USB storage driver implicit loading: Blocked" elif [ "${usb_storage_status}" == "Enabled" ] && [ "${uas_status}" == "Enabled" ]; then echo "[Info] USB storage driver implicit loading: Allowed" else echo "[Info] USB storage driver implicit loading: Partially Allowed" fi # Output the status of explicit loading if [ "${usb_storage_driver_status}" == "Disabled" ] && [ "${uas_driver_status}" == "Disabled" ]; then echo "[Info] USB storage driver manual loading: Blocked" elif [ "${usb_storage_driver_status}" == "Enabled" ] && [ "${uas_driver_status}" == "Enabled" ]; then echo "[Info] USB storage driver manual loading: Allowed" else echo "[Info] USB storage driver manual loading: Partially Allowed" fi # Determine the overall status of USB storage if [ "${usb_storage_status}" == "Disabled" ] && [ "${uas_status}" == "Disabled" ] && [ "${usb_storage_driver_status}" == "Disabled" ] && [ "${uas_driver_status}" == "Disabled" ]; then UsbStorage="Disabled" elif [ "${usb_storage_status}" == "Enabled" ] && [ "${uas_status}" == "Enabled" ] && [ "${usb_storage_driver_status}" == "Enabled" ] && [ "${uas_driver_status}" == "Enabled" ]; then UsbStorage="Enabled" elif [ "${usb_storage_status}" == "Enabled" ] || [ "${uas_status}" == "Enabled" ] || [ "${usb_storage_driver_status}" == "Enabled" ] || [ "${uas_driver_status}" == "Enabled" ]; then UsbStorage="Partially Allowed" else UsbStorage="Unable to Determine" fi echo "[Info] USB storage is ${UsbStorage}" if [[ -n "${_arg_customfield}" ]]; then SetCustomField "${_arg_customfield}" "${UsbStorage}" fi
Detailed Breakdown
This shell script performs several tasks in a structured and fault-tolerant way. Here’s a step-by-step breakdown:
-
Command-Line Argument Parsing
- Supports the optional -CustomField argument to specify a NinjaOne custom field.
- Provides a –help option for usage guidance.
-
Permission and Folder Checks
- Validates the existence and readability of the /etc/modprobe.d directory.
- If access is denied or the folder is missing, it reports “Unable to Determine” and, if configured, writes that to the NinjaOne custom field.
-
Driver Status Evaluation
- Iterates through each file in /etc/modprobe.d.
- Looks for:
- blacklist usb_storage or blacklist uas (blocks implicit loading).
- install usb-storage /bin/true or install uas /bin/true (blocks explicit loading).
- Tracks the status of each method per driver.
-
Status Determination
- Categorizes driver loading as Allowed, Blocked, or Partially Allowed.
- Aggregates the result into a final status of Enabled, Disabled, or Partially Allowed.
-
Integration with NinjaOne
- If -CustomField is specified and the ninjarmm-cli tool is present, the script writes the USB storage status to the designated custom field.
Potential Use Cases
Case Study:
A healthcare MSP manages hundreds of Linux-based kiosks in clinics. Security policy dictates that USB storage should be disabled to prevent unauthorized data extraction. Using this script, the MSP can:
- Deploy the script across all endpoints using NinjaOne.
- Automatically log USB status into a custom field.
- Generate compliance reports.
- Alert technicians if any machine is out of policy.
This proactive compliance monitoring saves hours of manual auditing and ensures regulatory adherence (e.g., HIPAA).
Comparisons
Other Methods
Approach | Pros | Cons |
Manual inspection (lsmod, grep) | Immediate, CLI-native | Time-consuming, non-scalable |
Audit scripts in Python | More flexible parsing | Heavier dependencies |
Endpoint Detection and Response (EDR) | Centralized control, real-time | High cost, often Windows-focused |
This Shell Script | Lightweight, integrates with NinjaOne | May require sudo and Linux-savvy techs |
This script’s niche is its simplicity, portability, and native integration with the NinjaOne ecosystem, making it ideal for environments where simplicity and automation trump customization.
Frequently Asked Questions
What Linux distributions are supported?
Most Debian- and Red Hat-based distributions with /etc/modprobe.d are compatible.
Does this script block USB storage?
No, it only checks the status. Blocking must be handled via system configuration.
What is ninjarmm-cli?
It’s NinjaOne’s command-line interface for reading/writing custom field data on endpoints.
What does “Partially Allowed” mean?
It indicates that some methods of driver loading are permitted while others are blocked.
Can I use this without NinjaOne?
Yes, but you won’t be able to save results to a custom field without ninjarmm-cli.
Implications
Understanding whether USB storage is allowed impacts your risk posture. Allowing unrestricted USB access increases the potential for data leakage, malware introduction, and accidental data transfers. In regulated industries—finance, healthcare, government—demonstrating control over USB access is often mandatory. This script enables such oversight in a minimally invasive, repeatable way.
Recommendations
- Run the script with elevated permissions to ensure complete access to /etc/modprobe.d.
- Standardize the custom field name across devices for unified reporting in NinjaOne.
- Pair this script with remediation tools that enforce blocking if non-compliance is detected.
- Schedule it as a recurring task within NinjaOne for periodic compliance validation.
Final Thoughts
Using shell scripting to check USB Storage Status in Linux provides a lightweight, auditable, and automatable method for endpoint compliance verification. When paired with NinjaOne, this script becomes a powerful tool for centralized policy enforcement and visibility. Whether you’re an MSP managing client fleets or an internal IT team securing corporate assets, this solution offers the right balance of simplicity and power.