2727# Options:
2828# --enable-npm-scan Enable Node.js package scanning
2929# --disable-npm-scan Disable Node.js package scanning
30+ # --search-dirs DIR [DIR...] Search DIRs instead of $HOME (replaces default; repeatable)
3031# --verbose Show progress messages
3132# --color=WHEN auto | always | never (default: auto)
3233# -v, --version Show version
@@ -41,7 +42,7 @@ set -euo pipefail
4142# SECTION 2: VERSION AND CLI DEFAULTS
4243# ==============================================================================
4344
44- AGENT_VERSION=" 1.8.1 "
45+ AGENT_VERSION=" 1.8.2 "
4546
4647# Output configuration (set by CLI flags)
4748OUTPUT_FORMAT=" pretty" # pretty | json | html
@@ -50,6 +51,14 @@ COLOR_MODE="auto" # auto | always | never
5051QUIET=true # Suppress progress messages by default in community mode
5152ENABLE_NODE_PACKAGE_SCAN=" auto" # auto | true | false
5253
54+ # Directories to search for projects and extensions (bash array)
55+ # Default: user's home directory. Customize as needed, e.g.:
56+ # SEARCH_DIRS=("\$HOME" "/Volumes/code") # home + encrypted partition
57+ # SEARCH_DIRS=("/Volumes/code") # only encrypted partition
58+ # SEARCH_DIRS=("\$HOME" "/Volumes/code" "/opt/work") # multiple locations
59+ SEARCH_DIRS=(" \$ HOME" )
60+ _SEARCH_DIRS_SET=false
61+
5362# ==============================================================================
5463# STEPSECURITY ENTERPRISE CONFIGURATION
5564# Community users: leave these unchanged. They are only used in enterprise mode.
@@ -524,6 +533,29 @@ get_user_directory() {
524533 return 0
525534}
526535
536+ resolve_search_directories () {
537+ local user_home=" $1 "
538+ local resolved_dirs=()
539+
540+ for dir in " ${SEARCH_DIRS[@]} " ; do
541+ # Resolve $HOME to the actual user home directory
542+ local resolved=" ${dir/ \$ HOME/ $user_home } "
543+ if [ -d " $resolved " ]; then
544+ resolved_dirs+=(" $resolved " )
545+ else
546+ print_progress " Warning: Search directory not found, skipping: $resolved "
547+ fi
548+ done
549+
550+ if [ ${# resolved_dirs[@]} -eq 0 ]; then
551+ print_progress " Warning: No valid search directories found, falling back to: $user_home "
552+ echo " $user_home "
553+ return
554+ fi
555+
556+ printf ' %s\n' " ${resolved_dirs[@]} "
557+ }
558+
527559# ==============================================================================
528560# LAUNCHD MANAGEMENT
529561# ==============================================================================
@@ -3086,12 +3118,30 @@ run_scan() {
30863118 fi
30873119 step_done " Scanning MCP server configs"
30883120
3089- # Collect extensions
3121+ # Resolve search directories
3122+ local search_dirs
3123+ search_dirs=$( resolve_search_directories " $user_home " )
3124+
3125+ # Collect extensions across all search directories
30903126 step_start " Scanning IDE extensions"
3091- local user_dir=" $user_home "
3092- local all_ext_result=$( collect_all_extensions " $user_dir " )
3093- local ide_extensions=$( echo " $all_ext_result " | head -1)
3094- local ext_count=$( echo " $all_ext_result " | tail -1)
3127+ local ide_extensions=" []"
3128+ local ext_count=0
3129+ while IFS= read -r search_dir; do
3130+ local dir_ext_result=$( collect_all_extensions " $search_dir " )
3131+ local dir_extensions=$( echo " $dir_ext_result " | head -1)
3132+ local dir_ext_count=$( echo " $dir_ext_result " | tail -1)
3133+ # Merge JSON arrays
3134+ if [ " $dir_extensions " != " []" ] && [ -n " $dir_extensions " ]; then
3135+ if [ " $ide_extensions " = " []" ]; then
3136+ ide_extensions=" $dir_extensions "
3137+ else
3138+ local existing_content=$( echo " $ide_extensions " | sed ' s/^\[//;s/\]$//' )
3139+ local new_content=$( echo " $dir_extensions " | sed ' s/^\[//;s/\]$//' )
3140+ ide_extensions=" [${existing_content} ,${new_content} ]"
3141+ fi
3142+ fi
3143+ ext_count=$(( ext_count + dir_ext_count))
3144+ done <<< " $search_dirs"
30953145 step_done " Scanning IDE extensions"
30963146
30973147 # Resolve ENABLE_NODE_PACKAGE_SCAN: in community mode, "auto" means "false"
@@ -3119,10 +3169,36 @@ run_scan() {
31193169 step_done " Scanning global packages"
31203170
31213171 step_start " Scanning Node.js projects"
3122- local node_scan_result=$( scan_node_projects " $user_dir " " $logged_in_user " )
3123- node_projects_file=$( echo " $node_scan_result " | sed -n ' 1p' )
3124- node_projects_count=$( echo " $node_scan_result " | sed -n ' 2p' )
3125- node_scan_duration=$( echo " $node_scan_result " | sed -n ' 3p' )
3172+ # Scan across all search directories, merge results
3173+ local combined_projects_file=$( mktemp)
3174+ echo " []" > " $combined_projects_file "
3175+ local total_node_projects_count=0
3176+ local total_node_scan_duration=0
3177+ while IFS= read -r search_dir; do
3178+ local node_scan_result=$( scan_node_projects " $search_dir " " $logged_in_user " )
3179+ local dir_projects_file=$( echo " $node_scan_result " | sed -n ' 1p' )
3180+ local dir_projects_count=$( echo " $node_scan_result " | sed -n ' 2p' )
3181+ local dir_scan_duration=$( echo " $node_scan_result " | sed -n ' 3p' )
3182+ total_node_projects_count=$(( total_node_projects_count + dir_projects_count))
3183+ total_node_scan_duration=$(( total_node_scan_duration + dir_scan_duration))
3184+ # Merge project files
3185+ if [ -n " $dir_projects_file " ] && [ -f " $dir_projects_file " ]; then
3186+ local existing=$( cat " $combined_projects_file " )
3187+ if [ " $existing " = " []" ]; then
3188+ cat " $dir_projects_file " > " $combined_projects_file "
3189+ else
3190+ local existing_content=$( echo " $existing " | sed ' s/^\[//;s/\]$//' )
3191+ local new_content=$( cat " $dir_projects_file " | sed ' s/^\[//;s/\]$//' )
3192+ if [ -n " $new_content " ]; then
3193+ echo " [${existing_content} ,${new_content} ]" > " $combined_projects_file "
3194+ fi
3195+ fi
3196+ rm -f " $dir_projects_file "
3197+ fi
3198+ done <<< " $search_dirs"
3199+ node_projects_file=" $combined_projects_file "
3200+ node_projects_count=$total_node_projects_count
3201+ node_scan_duration=$total_node_scan_duration
31263202 step_done " Scanning Node.js projects"
31273203 else
31283204 step_skip " Node.js packages (use --enable-npm-scan)"
@@ -3297,14 +3373,29 @@ EOF
32973373 local ide_installations=$( detect_ide_installations " $logged_in_user " )
32983374 echo " "
32993375
3300- # Get user directory to scan
3301- local user_dir=$( get_user_directory)
3376+ # Resolve search directories
3377+ local search_dirs
3378+ search_dirs=$( resolve_search_directories " $user_home " )
33023379 echo " "
33033380
3304- # Collect all IDE extensions
3305- local all_ide_collection_result=$( collect_all_extensions " $user_dir " )
3306- local ide_extensions=$( echo " $all_ide_collection_result " | head -1)
3307- local ide_extensions_count=$( echo " $all_ide_collection_result " | tail -1)
3381+ # Collect all IDE extensions across search directories
3382+ local ide_extensions=" []"
3383+ local ide_extensions_count=0
3384+ while IFS= read -r search_dir; do
3385+ local dir_ext_result=$( collect_all_extensions " $search_dir " )
3386+ local dir_extensions=$( echo " $dir_ext_result " | head -1)
3387+ local dir_ext_count=$( echo " $dir_ext_result " | tail -1)
3388+ if [ " $dir_extensions " != " []" ] && [ -n " $dir_extensions " ]; then
3389+ if [ " $ide_extensions " = " []" ]; then
3390+ ide_extensions=" $dir_extensions "
3391+ else
3392+ local existing_content=$( echo " $ide_extensions " | sed ' s/^\[//;s/\]$//' )
3393+ local new_content=$( echo " $dir_extensions " | sed ' s/^\[//;s/\]$//' )
3394+ ide_extensions=" [${existing_content} ,${new_content} ]"
3395+ fi
3396+ fi
3397+ ide_extensions_count=$(( ide_extensions_count + dir_ext_count))
3398+ done <<< " $search_dirs"
33083399 echo " "
33093400
33103401 # AI Agent Detection (v1.6.0+)
@@ -3375,11 +3466,35 @@ EOF
33753466 node_global_packages_count=$( echo " $global_scan_result " | sed -n ' 2p' )
33763467 echo " "
33773468
3378- # Scan for Node.js projects (returns file path)
3379- local node_scan_result=$( scan_node_projects " $user_dir " " $logged_in_user " )
3380- node_projects_file=$( echo " $node_scan_result " | sed -n ' 1p' )
3381- node_projects_count=$( echo " $node_scan_result " | sed -n ' 2p' )
3382- node_scan_duration=$( echo " $node_scan_result " | sed -n ' 3p' )
3469+ # Scan for Node.js projects across all search directories
3470+ local combined_projects_file=$( mktemp)
3471+ echo " []" > " $combined_projects_file "
3472+ local total_node_projects_count=0
3473+ local total_node_scan_duration=0
3474+ while IFS= read -r search_dir; do
3475+ local node_scan_result=$( scan_node_projects " $search_dir " " $logged_in_user " )
3476+ local dir_projects_file=$( echo " $node_scan_result " | sed -n ' 1p' )
3477+ local dir_projects_count=$( echo " $node_scan_result " | sed -n ' 2p' )
3478+ local dir_scan_duration=$( echo " $node_scan_result " | sed -n ' 3p' )
3479+ total_node_projects_count=$(( total_node_projects_count + dir_projects_count))
3480+ total_node_scan_duration=$(( total_node_scan_duration + dir_scan_duration))
3481+ if [ -n " $dir_projects_file " ] && [ -f " $dir_projects_file " ]; then
3482+ local existing=$( cat " $combined_projects_file " )
3483+ if [ " $existing " = " []" ]; then
3484+ cat " $dir_projects_file " > " $combined_projects_file "
3485+ else
3486+ local existing_content=$( echo " $existing " | sed ' s/^\[//;s/\]$//' )
3487+ local new_content=$( cat " $dir_projects_file " | sed ' s/^\[//;s/\]$//' )
3488+ if [ -n " $new_content " ]; then
3489+ echo " [${existing_content} ,${new_content} ]" > " $combined_projects_file "
3490+ fi
3491+ fi
3492+ rm -f " $dir_projects_file "
3493+ fi
3494+ done <<< " $search_dirs"
3495+ node_projects_file=" $combined_projects_file "
3496+ node_projects_count=$total_node_projects_count
3497+ node_scan_duration=$total_node_scan_duration
33833498 echo " "
33843499
33853500 else
@@ -3493,6 +3608,7 @@ Output formats (community mode, mutually exclusive):
34933608 --html FILE HTML report saved to FILE
34943609
34953610Options:
3611+ --search-dirs DIR [DIR...] Search DIRs instead of \$ HOME (replaces default; repeatable)
34963612 --enable-npm-scan Enable Node.js package scanning
34973613 --disable-npm-scan Disable Node.js package scanning
34983614 --verbose Show progress messages (suppressed by default)
@@ -3506,6 +3622,9 @@ Examples:
35063622 $( basename " $0 " ) --json > scan.json # JSON to file
35073623 $( basename " $0 " ) --html report.html # HTML report
35083624 $( basename " $0 " ) --verbose --enable-npm-scan # Verbose with npm scan
3625+ $( basename " $0 " ) --search-dirs /Volumes/code # Search only /Volumes/code
3626+ $( basename " $0 " ) --search-dirs /tmp /opt # Multiple dirs, one flag
3627+ $( basename " $0 " ) --search-dirs "/path/with spaces" --search-dirs /opt # Mixed styles
35093628 $( basename " $0 " ) send-telemetry # Enterprise telemetry
35103629
35113630https://github.com/step-security/dev-machine-guard
@@ -3561,6 +3680,21 @@ while [ $# -gt 0 ]; do
35613680 fi
35623681 shift
35633682 ;;
3683+ --search-dirs)
3684+ shift
3685+ if [ $# -eq 0 ] || [[ " $1 " == --* ]]; then
3686+ print_error " --search-dirs requires at least one directory path argument"
3687+ exit 1
3688+ fi
3689+ if [ " $_SEARCH_DIRS_SET " = false ]; then
3690+ SEARCH_DIRS=()
3691+ _SEARCH_DIRS_SET=true
3692+ fi
3693+ while [ $# -gt 0 ] && [[ " $1 " != --* ]]; do
3694+ SEARCH_DIRS+=(" $1 " )
3695+ shift
3696+ done
3697+ ;;
35643698 --verbose)
35653699 QUIET=false
35663700 shift
0 commit comments