├── .github ├── dependabot.yml └── workflows │ └── Shellcheck.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-expand ├── c0design ├── cleanup ├── debug ├── detect-vmware ├── dump-target-domains ├── enable-touchid-sudo ├── entitlements ├── README.md ├── dump-entitlements └── treebeard ├── legacy-check ├── list-apps ├── office-macro-security ├── pkg-expand ├── quarantine_score ├── test └── uuid /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | # Set update schedule for GitHub Actions 7 | version: 2 8 | updates: 9 | - package-ecosystem: "github-actions" # See documentation for possible values 10 | directory: "/" # Location of package manifests 11 | schedule: 12 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/Shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Shellcheck 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "shellcheck" 14 | shellcheck: 15 | # The type of runner that the job will run on 16 | runs-on: macos-latest 17 | 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | # Checks-out your repository under ${{ github.workspace }}, so your job can access it 21 | - uses: actions/checkout@v4 22 | 23 | - name: Download Shellcheck 24 | env: 25 | SHELLCEHCK_LATEST_VERSION: "0.10.0" 26 | run: | 27 | 28 | curl --silent --location --output "${{ github.workspace }}/shellcheck-v${SHELLCEHCK_LATEST_VERSION}.tar.xz" \ 29 | "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCEHCK_LATEST_VERSION}/shellcheck-v${SHELLCEHCK_LATEST_VERSION}.darwin.aarch64.tar.xz" 30 | 31 | tar -xf "${{ github.workspace }}/shellcheck-v${SHELLCEHCK_LATEST_VERSION}.tar.xz" 32 | mv "${{ github.workspace }}/shellcheck-v${SHELLCEHCK_LATEST_VERSION}/shellcheck" \ 33 | "${{ github.workspace }}/shellcheck" 34 | 35 | - name: Print shellcheck version 36 | run: | 37 | "${{ github.workspace }}/shellcheck" --version 38 | 39 | - name: Execute test 40 | run: | 41 | declare -a ERRORS 42 | declare -a FILES 43 | 44 | while IFS=$'\n' read -r file; do 45 | FILES+=("${file}"); 46 | done < <(/usr/bin/find "${{ github.workspace }}" -maxdepth 1 -type f \ 47 | -not -iwholename '*.git*' \ 48 | -not -iwholename '*venv*' \ 49 | -not -iwholename '*.tar.xz' \ 50 | -not -name 'shellcheck' \ 51 | | /usr/bin/sort -u) 52 | 53 | for file in "${FILES[@]}"; do 54 | if /usr/bin/file "${file}" | /usr/bin/grep --quiet "shell" || \ 55 | /usr/bin/file "${file}" | /usr/bin/grep --quiet "bash" || \ 56 | /usr/bin/file "${file}" | /usr/bin/grep --quiet "zsh"; then 57 | 58 | if "${{ github.workspace }}/shellcheck" --shell=bash "${file}" ; then 59 | echo "[PASS] $(/usr/bin/basename "${file}")" 60 | else 61 | echo "[FAIL] $(/usr/bin/basename "${file}")" 62 | ERRORS+=("${file}") 63 | fi 64 | fi 65 | done 66 | 67 | if [[ ${#ERRORS[@]} -eq 0 ]]; then 68 | # If ERRORS empty then 69 | echo "[PASS] No errors, hooray" 70 | exit 0 71 | else 72 | # If ERRORS not empty, print the names of files which failed 73 | echo "[FAIL] These files failed linting: ${ERRORS[*]}" 74 | exit 1 75 | fi 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 0xmachos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macos-scripts 2 | Various scripts for macOS tasks 3 | 4 | ![Shellcheck](https://github.com/0xmachos/macos-scripts/workflows/Shellcheck/badge.svg) 5 | 6 | 7 | ### [`c0design`](https://github.com/0xmachos/macos-scripts/blob/master/c0design) 8 | - Wrapper for `codesign`, `spctl`, `stapler` & `pkgutil` 9 | - Check codesigning and notarisation of application or package (`.pkg`) at `$path_to_verify` 10 | - Equivalent of `codesign --verify --deep --strict` && `spctl --assess --type exec` 11 | - Equivalent of `pkgutil --check-signature` && `spctl --assess --type install` 12 | - Check codesigning and notarisation of all non system (thirdparty) applications 13 | - Usage: `./c0design {usage | list | verify $path_to_verify | thirdparty}` 14 | 15 | 16 | ### [`debug`](https://github.com/0xmachos/macos-scripts/blob/master/debug) 17 | - Print some debug info about the current system 18 | - Usage: `./debug` 19 | - Usage: `./debug | pbcopy` (Copies output to the clipboard) 20 | 21 | 22 | ### [`detect-vmware`](https://github.com/0xmachos/macos-scripts/blob/master/detect-vmware) 23 | - Try to determine if being run in a VMware Virtual Machine 24 | - Usage: `./detect-vmware` 25 | 26 | 27 | ### [`legacy-check`](https://github.com/0xmachos/macos-scripts/blob/master/legacy-check) 28 | - Check system for 32-bit applications & third party kernel extensions 29 | - Usage: `./legacy-check` 30 | 31 | 32 | ### [`office-macro-security`](https://github.com/0xmachos/macos-scripts/blob/master/office-macro-security) 33 | - Disable/ restrict Microsoft Office macro execution 34 | - Usage: `./office-macro-security {list | disable | restrict | both | undo}` 35 | 36 | 37 | ### [`pkg-expand`](https://github.com/0xmachos/macos-scripts/blob/master/pkg-expand) 38 | - Expands package (`--expand-full`) 39 | - Finds and expands embeded packages 40 | - Dumps Bill of Material (Bom) files 41 | - Usage: `./pkg-expand /tmp/example.pkg` 42 | 43 | -------------------------------------------------------------------------------- /app-expand: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # macos-scripts/app-expand 3 | 4 | set -uo pipefail 5 | # -u prevent using undefined variables 6 | # -o pipefail force pipelines to fail on first non-zero status code 7 | 8 | IFS=$'\n\t' 9 | # Set Internal Field Separator to newlines and tabs 10 | # This makes bash consider newlines and tabs as separating words 11 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 12 | 13 | ### Define Colours ### 14 | tput sgr0; 15 | # reset colors 16 | 17 | 18 | function usage { 19 | 20 | echo -e "\\n Expand Application Bundles (.app)" 21 | echo -e "Recursively checks app bundles for Mach-O files" 22 | echo " ./app-expand example.app" 23 | 24 | exit 0 25 | } 26 | 27 | 28 | function file_search { 29 | while IFS=$'\n' read -r file; do 30 | files+=("${file}"); 31 | done < <(find "${arg}" -type f) 32 | } 33 | 34 | 35 | function macho_check { 36 | 37 | echo "Mach-O Files: " 38 | 39 | for file in "${files[@]}"; do 40 | if file "${file}" | grep -q 'Mach-O'; then 41 | macho_files+=("${file}"); 42 | shasum -a 256 "${file}" 43 | fi 44 | done 45 | } 46 | 47 | 48 | function main { 49 | declare -a files 50 | declare -a macho_files 51 | 52 | arg=${1:-"usage"} 53 | 54 | if [[ "${arg}" =~ ^(usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯")$ ]]; then 55 | usage 56 | fi 57 | 58 | file_search 59 | macho_check 60 | } 61 | 62 | main "$@" 63 | -------------------------------------------------------------------------------- /c0design: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/c0design 3 | 4 | # c0design 5 | # Wrapper for codesign, spctl, stapler & pkgutil 6 | # Check code signature and notarisation of applications and packages 7 | # See Howard Oakley's (@howardnoakley) work: 8 | # https://eclecticlight.co/2019/05/31/can-you-tell-whether-code-has-been-notarized/ 9 | # https://eclecticlight.co/2020/10/30/code-signatures-2-how-to-check-them/ 10 | 11 | set -uo pipefail 12 | # -o pipefail force pipelines to fail on first non-zero status code 13 | # -u prevent using undefined variables 14 | 15 | IFS=$'\n\t' 16 | # Set Internal Field Separator to newlines and tabs 17 | # This makes bash consider newlines and tabs as separating words 18 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 19 | 20 | ### Define Colours ### 21 | tput sgr0; 22 | # reset colors 23 | 24 | RESET=$(tput sgr0) 25 | readonly RESET 26 | RED=$(tput setaf 1) 27 | readonly RED 28 | YELLOW=$(tput setaf 3) 29 | readonly YELLOW 30 | GREEN=$(tput setaf 64) 31 | readonly GREEN 32 | ### END Colours ### 33 | 34 | 35 | function usage { 36 | echo 37 | echo " Wrapper for codesign, spctl, stapler & pkgutil" 38 | echo " Usage: ./c0design {usage | list | verify \$path_to_verify | thirdparty}" 39 | echo 40 | 41 | echo " list List all non system applications (excludes Xcode & Safari)" 42 | echo " verify Check code signing and notarisation of application or package at \$path_to_verify" 43 | echo " Equivalent of codesign --verify --deep --strict && spctl --assess --type exec" 44 | echo " Equivalent of pkgutil --check-signature && spctl --assess --type install" 45 | echo " thirdparty Check code signing and notarisation of all non system applications" 46 | echo 47 | 48 | exit 0 49 | } 50 | 51 | 52 | ### Utility Functions ### 53 | # ctrl_c 54 | 55 | function ctrl_c { 56 | echo -e "\\n[❌] ${USER} has chosen to quit!" 57 | exit 1 58 | } 59 | 60 | ### END Utility Functions ### 61 | 62 | 63 | function get_applications { 64 | # get_applications 65 | # Get list of all applications on system via system_profiler 66 | # Ignore system applications, Xcode and Safari 67 | # Produces array, $apps, of non system apps 68 | 69 | while IFS=$'\n' read -r app; do 70 | 71 | if [[ "${app}" =~ /System/ ]]; then 72 | continue 73 | # Skip everything in /System/Applications/, /System/Library/ and /Library/Apple/System/ 74 | # since they can't be third party apps 75 | # Because of /Library/Apple/System do not use start-of-line anchor in regex e.g ^/System/ 76 | elif [[ "${app}" =~ Xcode.app ]]; then 77 | continue 78 | # Skip Xcode because it takes ages to run codesign on it 79 | elif [[ "${app}" =~ Safari.app ]]; then 80 | continue 81 | # Skip Safari because it is a system application 82 | fi 83 | 84 | apps+=("${app}"); 85 | # e.g. Adds "/Applications/1Password 7.app" to apps array 86 | 87 | done < <(system_profiler SPApplicationsDataType \ 88 | | grep "Location: " \ 89 | | awk -F ': ' '{print $2}') 90 | } 91 | 92 | 93 | function verify_app_signature { 94 | # verify_app_signature 95 | # Verify that app at $path_to_verify is correctly code signed 96 | # Save codesign output in $codesign_output for printing errors 97 | 98 | declare -r path_to_verify=${1:?path_to_verify not passed to verify_app_signature} 99 | 100 | if codesign_output="$(/usr/bin/codesign --verify --deep --strict "${path_to_verify}" 2>&1)"; then 101 | # codesign sends all output to stderr, redirect it to std out 102 | return 0 103 | elif [[ "${codesign_output}" =~ No\ such\ file\ or\ directory ]]; then 104 | echo "${path_to_verify}: No such file or directory" 105 | exit 1 106 | else 107 | return 1 108 | fi 109 | } 110 | 111 | 112 | function check_app_notarsied { 113 | # check_app_notarsied 114 | # Check if app is allowed to be executed under current system policy 115 | # On systems with default settings this will check if app is notarised 116 | # If SecAssessment has been disabled spctl notarisation checks will fail 117 | # If system has SecAssessment disabled (e.g. spctl --master-disable has been executed) 118 | # you can execute codesign --test-requirement="=notarized" --verify to check notarisation 119 | 120 | declare -r path_to_verify=${1:?path_to_verify not passed to check_app_notarsied} 121 | 122 | if /usr/sbin/spctl --assess --type exec "${path_to_verify}" >/dev/null 2>&1; then 123 | # Check if system policy allows execution of this code aka Notarised 124 | return 0 125 | else 126 | return 1 127 | fi 128 | } 129 | 130 | 131 | function check_stapled_notarisation_ticket { 132 | # check_stapled_notarisation_ticket 133 | # Check if the application or package at $path_to_verify has a locally stapled 134 | # notarisation ticket 135 | # Not having a locally stapled ticket is NOT a security issue, Apple can 136 | # hold the ticket on their servers with macOS reaching out to Apple to 137 | # validate the ticket. 138 | # See Howard Oakley's (@howardnoakley) tweet: 139 | # https://twitter.com/howardnoakley/status/1369328846466650120 140 | 141 | declare -r path_to_verify=${1:?path_to_verify not passed to check_stapled_notarisation_ticket} 142 | 143 | if /usr/bin/stapler validate "${path_to_verify}" >/dev/null 2>&1; then 144 | # Check if app has a notarisation ticket stapled to it 145 | return 0 146 | else 147 | return 1 148 | fi 149 | } 150 | 151 | 152 | function check_if_app_from_app_store { 153 | # check_if_app_from_app_store 154 | # Checks if app is from Mac App Store 155 | # or from outside App Store based on intermediate certificate used 156 | # to sign the leaf certificate 157 | # Could also use 158 | # mdls "${path_to_verify}" -name kMDItemAppStoreHasReceipt 159 | 160 | declare -r path_to_verify=${1:?path_to_verify not passed to check_if_app_from_app_store} 161 | codesign_output=$(/usr/bin/codesign --display --verbose=2 "${path_to_verify}" 2>&1) 162 | 163 | if echo "${codesign_output}" | grep -q "Authority=Apple Mac OS Application Signing"; then 164 | return 0 165 | 166 | elif echo "${codesign_output}" | grep -q "Authority=Developer ID Certification Authority"; then 167 | return 1 168 | fi 169 | } 170 | 171 | 172 | function verify_app { 173 | # verify_app 174 | # Use the above helper functions to check application 175 | # 1. Properly signed 176 | # 2. Notarised 177 | # 3. Locally stapled notarisation ticket 178 | # 4. From Mac App Store or Developer ID App 179 | # echo to command line based on above criteria 180 | 181 | declare -r path_to_verify=${1:?path_to_verify not passed to verify_app} 182 | path_basename=$(/usr/bin/basename "${path_to_verify}") 183 | 184 | if verify_app_signature "${path_to_verify}"; then 185 | 186 | if check_app_notarsied "${path_to_verify}"; then 187 | 188 | if check_stapled_notarisation_ticket "${path_to_verify}"; then 189 | 190 | if check_if_app_from_app_store "${path_to_verify}"; then 191 | echo "${path_basename} ${GREEN}signed, properly verified & notarised${RESET} (stapled ticket) (App Store)" 192 | # Wording from man page is "all codes verified properly as requested" 193 | else 194 | echo "${path_basename} ${GREEN}signed, properly verified & notarised${RESET} (stapled ticket) (Developer ID)" 195 | fi 196 | 197 | else 198 | 199 | if check_if_app_from_app_store "${path_to_verify}"; then 200 | echo "${path_basename} ${GREEN}signed, properly verified & notarised${RESET} ${YELLOW}(no stapled ticket)${RESET} (App Store)" 201 | # Apps from the AppStore never have stapled tickets 202 | else 203 | echo "${path_basename} ${GREEN}signed, properly verified & notarised${RESET} ${YELLOW}(no stapled ticket)${RESET} (Developer ID)" 204 | # Developer ID apps can have stapled tickets, not a requiremnt though 205 | fi 206 | fi 207 | 208 | else 209 | echo "${path_basename} ${GREEN}signed and properly verified${RESET} but ${RED}not notarised${RESET}" 210 | fi 211 | 212 | else 213 | codesign_error_output=$(echo "${codesign_output}" | head -n 1 | awk -F ':' '{print $2}') 214 | # head -n 1 removes the "In architecture" output which indicates in a Fat/Universal binary which 215 | # specific binary has failed code signing checks. 216 | # Output is either x86_64 or arm64e 217 | # Currently don't care about which binary has fialed code signing checks just that one or the other has 218 | echo "${path_basename} ${RED}${codesign_error_output}${RESET}" 219 | return 1 220 | fi 221 | } 222 | 223 | 224 | function verify_package_signature { 225 | # verify_package_signature 226 | # Verify that package (.pkg) at $path_to_verify is correctly code signed 227 | # Save codesign output in $pkgutil_error_output for printing errors 228 | 229 | if pkgutil_output="$(/usr/sbin/pkgutil --check-signature "${path_to_verify}")"; then 230 | return 0 231 | else 232 | return 1 233 | fi 234 | } 235 | 236 | 237 | function check_package_notarised { 238 | # check_package_notarised 239 | # Check if package is allowed to be run and install under current system policy 240 | # On systems with default settings this will check if app is notarised 241 | # If SecAssessment has been disabled spctl notarisation checks will fail 242 | # If system has SecAssessment disabled (e.g. spctl --master-disable has been executed) 243 | # you can execute codesign --test-requirement="=notarized" --verify to check notarisation 244 | 245 | declare -r path_to_verify=${1:?path_to_verify not passed to check_package_notarised} 246 | 247 | if /usr/sbin/spctl --assess --type install "${path_to_verify}"; then 248 | return 0 249 | else 250 | return 1 251 | fi 252 | } 253 | 254 | 255 | function verify_pkg { 256 | # verify_pkg 257 | # Verify that package at $path_to_verify is correctly code signed 258 | # Save pkgutil output in $pkgutil_output for priting errors 259 | # Verify that package is notarised via spctl 260 | 261 | declare -r path_to_verify=${1:?path_to_verify not passed to verify_pkg} 262 | path_basename=$(/usr/bin/basename "${path_to_verify}") 263 | 264 | if verify_package_signature "${path_to_verify}"; then 265 | 266 | if check_package_notarised "${path_to_verify}"; then 267 | 268 | if check_stapled_notarisation_ticket "${path_to_verify}"; then 269 | echo "${path_basename} ${GREEN}signed, properly verified & notarised${RESET} (stapled ticket)" 270 | else 271 | echo "${path_basename} ${GREEN}signed, properly verified & notarised${RESET} ${YELLOW}(no stapled ticket)${RESET}" 272 | fi 273 | 274 | else 275 | echo "${path_basename} ${GREEN}signed, properly verified but ${RED}not notarised{$RESET}" 276 | fi 277 | 278 | else 279 | pkgutil_error_output=$(echo "${pkgutil_output}" | tail -n +2 | awk -F ':' '{print $2}') 280 | # tail -n +2 removes the first line of output "Package "install2.pkg":" 281 | # I've only tested this on "no signature" output so might be fucked for other error output 282 | # Use pkgutil to expand then flatten a package to reproduce 283 | echo "${path_basename} ${RED}${pkgutil_error_output}${RESET}" 284 | return 1 285 | fi 286 | } 287 | 288 | 289 | function main { 290 | 291 | trap ctrl_c SIGINT 292 | # Detect and react to the user hitting CTRL + C 293 | 294 | declare -r arg=${1:-"usage"} 295 | declare -r path_to_verify=${2:-"null"} 296 | # Set $path_to_verify to "null" if argv[2] is null or unset 297 | # This is a probably a dirty hack that needs a proper solution 298 | # Should be able to use parameter expansion but bug when using ${2+} which 299 | # causes $path_to_verify to be set to empty even when argv[2] 300 | # is not empty. i.e. with ${2+} ./c0design verify aaa 301 | # for some reason it thinks argv[2] empty 302 | declare -a apps 303 | declare codesign_output 304 | declare codesign_error_output 305 | declare pkgutil_output 306 | declare pkgutil_error_output 307 | 308 | case "${arg}" in 309 | 310 | usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯") 311 | usage 312 | ;; 313 | 314 | list|-l|--list) 315 | get_applications 316 | 317 | for app in "${apps[@]}"; do 318 | echo "${app}" 319 | done 320 | ;; 321 | 322 | verify|-v|--verify) 323 | if [[ "${path_to_verify}" == "null" ]]; then 324 | usage 325 | fi 326 | 327 | if [[ "${path_to_verify}" =~ .pkg ]]; then 328 | verify_pkg "${path_to_verify}" 329 | else 330 | verify_app "${path_to_verify}" 331 | fi 332 | ;; 333 | 334 | thirdparty|-tp|-3p|--third-party) 335 | get_applications 336 | 337 | for app in "${apps[@]}"; do 338 | verify_app "${app}" 339 | done 340 | # Execution time roughly ~60 seconds 341 | # Dependent on number and size of apps 342 | ;; 343 | 344 | *) 345 | usage 346 | ;; 347 | esac 348 | 349 | } 350 | 351 | main "$@" 352 | -------------------------------------------------------------------------------- /cleanup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # macos-scripts/cleanup 3 | 4 | # cleanup 5 | # Purge some caches and take out the trash 6 | 7 | set -euo pipefail 8 | # -e exit if any command returns non-zero status code 9 | # -u prevent using undefined variables 10 | # -o pipefail force pipelines to fail on first non-zero status code 11 | 12 | IFS=$'\n\t' 13 | # Set Internal Field Separator to newlines and tabs 14 | # This makes bash consider newlines and tabs as separating words 15 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 16 | 17 | ### UTILITY FUNCTIONS ### 18 | # check_developer_tools 19 | 20 | 21 | function check_developer_tools { 22 | 23 | if ! xcode-select -p >/dev/null 2>&1; then 24 | echo "[❌] Command line developer tools required" 25 | echo "[🍺] Install via: xcode-select --install" 26 | exit 1 27 | fi 28 | } 29 | 30 | 31 | ### END UTILITY FUNCTIONS ### 32 | 33 | 34 | function clean_xcode { 35 | # Delete old iOS simulators 36 | 37 | if check_developer_tools; then 38 | echo "[🗑] Deleting old Xcode iOS Simulators" 39 | 40 | xcrun simctl delete unavailable 41 | fi 42 | } 43 | 44 | 45 | function clean_brew { 46 | # Remove outdated downloads for formulae and casks, 47 | # and remove old versions of installed formulae. 48 | # Scrub the cache of the latest versions 49 | 50 | 51 | if [ -x "$(command -v brew)" ]; then 52 | echo "[🗑] Cleaning Brew" 53 | 54 | brew cleanup -s 55 | # Remove cache for installed formula and cask 56 | rm -rf "$(brew --cache)" 57 | fi 58 | } 59 | 60 | 61 | function clean_ruby { 62 | # Remove old versions of gems 63 | # that are not required to meet a dependency. 64 | 65 | if [ -x "/usr/local/opt/ruby/bin/gem" ]; then 66 | echo "[🗑] Cleaning Ruby Gems" 67 | 68 | /usr/local/opt/ruby/bin/gem cleanup >/dev/null 2>&1 69 | fi 70 | } 71 | 72 | 73 | function empty_trash { 74 | 75 | echo "[🗑] Emptying Trash" 76 | 77 | rm -rf "${HOME:?}/.Trash/"* 78 | } 79 | 80 | 81 | function main { 82 | 83 | clean_brew 84 | clean_ruby 85 | empty_trash 86 | } 87 | 88 | main "$@" 89 | -------------------------------------------------------------------------------- /debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/debug 3 | 4 | # debug 5 | # Print some debug info about the current system 6 | 7 | set -uo pipefail 8 | # -u prevent using undefined variables 9 | # -o pipefail force pipelines to fail on first non-zero status code 10 | 11 | IFS=$'\n\t' 12 | # Set Internal Field Separator to newlines and tabs 13 | # This makes bash consider newlines and tabs as separating words 14 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 15 | 16 | ### UTILITY FUNCTIONS ### 17 | # ctrl_c 18 | # usage 19 | 20 | function ctrl_c { 21 | # shellcheck disable=SC2317 22 | echo -e "\\n[❌] ${USER} has chosen to quit!" 23 | # shellcheck disable=SC2317 24 | exit 1 25 | } 26 | 27 | 28 | function usage { 29 | echo -e "\\n Print some debug info about the current system" 30 | echo -e " ./debug | pbcopy\\n" 31 | 32 | exit 0 33 | } 34 | 35 | ### END UTILITY FUNCTIONS ### 36 | 37 | 38 | function timestamp { 39 | 40 | echo "Timestamp: $(date)" 41 | } 42 | 43 | 44 | function version_info { 45 | 46 | build=$(sw_vers | grep "Build" | awk '{print $2}') 47 | version=$(sw_vers | grep "ProductVersion" | awk '{print $2}') 48 | 49 | echo "macOS: ${version} (Build: ${build})" 50 | } 51 | 52 | 53 | function is_admin { 54 | 55 | if groups | grep -q 'admin'; then 56 | echo "Account Type: Administrator" 57 | else 58 | echo "Account Type: Standard User" 59 | fi 60 | } 61 | 62 | 63 | function hardware_info { 64 | 65 | hardware_data=$(system_profiler SPHardwareDataType) 66 | model=$(echo "${hardware_data}" | grep "Identifier" | awk '{print $3}') 67 | ram=$(echo "${hardware_data}" | grep "Memory: " | awk -F ':' '{print $2}') 68 | processor_name=$(echo "${hardware_data}" | grep "Processor Name: " | awk -F ':' '{print $2}') 69 | processor_speed=$(echo "${hardware_data}" | grep "Processor Speed: " | awk -F ':' '{print $2}') 70 | 71 | echo "Model: ${model}" 72 | echo "RAM: ${ram}" 73 | 74 | echo "Processor: 75 | ${processor_name} 76 | ${processor_speed}" 77 | } 78 | 79 | 80 | function time_stats { 81 | 82 | last_reboot=$(last reboot | head -1 | awk -F " " '{print $2}') 83 | uptime=$(uptime | awk -F ',' '{print $1}' | awk -F 'up' '{print $2}') 84 | 85 | echo "Boot time: ${last_reboot}" 86 | echo "Uptime: ${uptime}" 87 | } 88 | 89 | 90 | function filesystem_info { 91 | 92 | spotlight_status=$(mdutil -s / | grep 'Indexing' | awk '{print $2}' | tr -d '.') 93 | fs_type=$(diskutil info / | grep 'Type (Bundle):' | awk '{print $3}') 94 | 95 | echo "File System Type: ${fs_type}" 96 | echo "Spotlight status for '/': ${spotlight_status}" 97 | } 98 | 99 | 100 | function security_info { 101 | 102 | gatekeeper_status=$(spctl --status | awk '{print $2}') 103 | kext_loading_stats=$(spctl kext-consent status | awk -F ': ' '{print $2}') 104 | sip_status=$(csrutil status | awk -F ': ' '{print $2}' | tr -d '.') 105 | filevault_status=$(fdesetup status | awk '{print $3}' | tr -d '.') 106 | 107 | echo "Gatekeeper Status: ${gatekeeper_status}" 108 | echo "Kernel Extension User Consent: ${kext_loading_stats}" 109 | echo "System Integrity Protection: ${sip_status}" 110 | echo "FileVault status: ${filevault_status}" 111 | 112 | # if system_profiler SPiBridgeDataType | grep 'Model Name:' | grep -q 'T2'; then 113 | # : 114 | # else 115 | # if /usr/libexec/firmwarecheckers/eficheck/eficheck --integrity-check >/dev/null 2>&1; then 116 | # echo "EFI Firmware: " 117 | # fi 118 | # fi 119 | } 120 | 121 | 122 | function full_disk_access_check { 123 | 124 | if [ -r "/$HOME/Library/Mail" ]; then 125 | echo "Terminal Full Disk Access: Enabled" 126 | else 127 | echo "Terminal Full Disk Access: Disabled" 128 | fi 129 | } 130 | 131 | 132 | function launchd_stuff { 133 | 134 | echo "User Launchd Processes:" 135 | launchctl list | grep -v 'com.apple' | tail -n +2 | awk '{print $3}' 136 | } 137 | 138 | 139 | function third_party_kexts { 140 | 141 | echo "Third party Kernel Extensions:" 142 | kextstat | grep -v 'com.apple' | tail -1 | awk '{print $6}' 143 | } 144 | 145 | 146 | function main { 147 | 148 | trap ctrl_c SIGINT 149 | # Detect and react to the user hitting CTRL + C 150 | 151 | declare -r arg=${1:-""} 152 | 153 | case "${arg}" in 154 | 155 | usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯") 156 | usage 157 | ;; 158 | 159 | *) 160 | timestamp 161 | version_info 162 | is_admin 163 | hardware_info 164 | time_stats 165 | filesystem_info 166 | security_info 167 | full_disk_access_check 168 | launchd_stuff 169 | third_party_kexts 170 | exit 0 171 | ;; 172 | 173 | esac 174 | } 175 | 176 | main "$@" 177 | -------------------------------------------------------------------------------- /detect-vmware: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/detect-vmware 3 | 4 | # detect-vmware 5 | # Try to determine if we're in a VMware Virtual Machine 6 | # https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/AntiVM/VMWare.cpp 7 | 8 | set -uo pipefail 9 | # -u prevent using undefined variables 10 | # -o pipefail force pipelines to fail on first non-zero status code 11 | 12 | IFS=$'\n\t' 13 | # Set Internal Field Separator to newlines and tabs 14 | # This makes bash consider newlines and tabs as separating words 15 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 16 | 17 | 18 | function check_mac_address { 19 | 20 | # VMware, Inc MAC address vendor identifiers 21 | # 00:05:69, 00:0c:29, 00:1C:14, 00:50:56 22 | 23 | echo "[👀] Checking Network Adaptor MAC Address(es)" 24 | 25 | if wifi_mac="$(networksetup -getmacaddress wi-fi | awk '{print $3}')"; then 26 | if [[ "${wifi_mac}" =~ 00:05:69\|00:0c:29\|00:1C:14\|00:50:56 ]]; then 27 | echo "[⚠️ ] Probably VMware" 28 | echo "[🤔] However, there is a Wi-Fi interface so maybe not?" 29 | fi 30 | fi 31 | 32 | if ethernet_mac="$(networksetup -getmacaddress ethernet | awk '{print $3}')"; then 33 | if [[ "${ethernet_mac}" =~ 00:05:69\|00:0c:29\|00:1C:14\|00:50:56 ]]; then 34 | echo "[⚠️ ] Probably VMware" 35 | fi 36 | fi 37 | echo 38 | } 39 | 40 | function check_shared_folders { 41 | 42 | echo "[👀] Checking for Shared Folders Directory" 43 | 44 | if [[ -d "/Volumes/VMware Shared Folders" ]]; then 45 | echo "[⚠️ ] Probably VMware" 46 | fi 47 | 48 | echo 49 | } 50 | 51 | function check_kexts { 52 | 53 | echo "[👀] Checking Installed Kexts" 54 | 55 | if [[ -d "/Library/Extensions/VMwareGfx.kext" ]]; then 56 | echo "[⚠️ ] Probably VMware" 57 | fi 58 | 59 | echo 60 | echo "[👀] Checking Loaded Kexts" 61 | 62 | if kextstat | grep -q 'com.vmware.kext.VMwareGfx' || kextstat | grep -q 'com.vmware.kext.vmhgfs'; then 63 | echo "[⚠️ ] Probably VMware" 64 | fi 65 | 66 | echo 67 | } 68 | 69 | function check_drivers { 70 | 71 | echo "[👀] Checking Drivers via I//O Registry" 72 | 73 | if ioreg | grep "VMware" >/dev/null; then 74 | # TODO: Absolutely no idea why grep "VMware" doesnt work 75 | echo "[⚠️ ] Probably VMware" 76 | fi 77 | 78 | echo 79 | } 80 | 81 | function check_hardware_info { 82 | 83 | echo "[👀] Checking System Hardware Data" 84 | 85 | hardware_data=$(system_profiler SPHardwareDataType) 86 | 87 | if echo "${hardware_data}" | grep -q 'VMware' || echo "${hardware_data}" | grep -q 'Virtual Machine' ; then 88 | echo "[⚠️ ] Probably VMware" 89 | fi 90 | } 91 | 92 | function main { 93 | 94 | check_mac_address 95 | check_shared_folders 96 | check_kexts 97 | check_drivers 98 | check_hardware_info 99 | } 100 | 101 | main "$@" 102 | -------------------------------------------------------------------------------- /dump-target-domains: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/dump-target-domains 3 | 4 | # dump-target-domain 5 | # Dumps the target domain (e.g gui/501) of all non Apple services 6 | # This relies on the "hints" provided by launchctl e.g 7 | # $ launchctl print at.obdev.littlesnitchmini.helper 8 | # Unrecognized target specifier, did you mean 9 | # gui/501/at.obdev.littlesnitchmini.helperx 10 | 11 | IFS=$'\n\t' 12 | # Set Internal Field Separator to newlines and tabs 13 | # This makes bash consider newlines and tabs as separating words 14 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 15 | 16 | 17 | function main { 18 | 19 | SERVICES=() 20 | 21 | while read -r service; do 22 | SERVICES+=("${service}"); 23 | done < <(launchctl list | grep -v 'com.apple' | tail -n +2 | awk '{print $3}') 24 | 25 | for service in "${SERVICES[@]}"; do 26 | launchctl blame "${service}" 2>&1 | grep 'did you mean' -A 1 | tail -n 1 27 | done 28 | 29 | } 30 | 31 | main "$@" 32 | -------------------------------------------------------------------------------- /enable-touchid-sudo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/enable-touchid-sudo 3 | 4 | # enable-touchid-sudo 5 | # Does what it says on the tin 6 | # See enable_touchid_sudo function comments for details 7 | 8 | set -euo pipefail 9 | # -e exit if any command returns non-zero status code 10 | # -u prevent using undefined variables 11 | # -o pipefail force pipelines to fail on first non-zero status code 12 | 13 | IFS=$'\n\t' 14 | # Set Internal Field Separator to newlines and tabs 15 | # This makes bash consider newlines and tabs as separating words 16 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 17 | 18 | tput sgr0; 19 | # reset colors 20 | 21 | ### Utility Functions ### 22 | # ctrl_c 23 | 24 | function ctrl_c { 25 | echo -e "\\n[❌] ${USER} has chosen to quit!" 26 | exit 1 27 | } 28 | ### END Utility Functions ### 29 | 30 | 31 | function enable_touchid_sudo { 32 | # enable_touchid_sudo 33 | # As of macOS Sonoma (14.0) this setting can persist OS updates when set in 34 | # /etc/pam.d/sudo_local. See: https://support.apple.com/en-us/HT213893 35 | # Check if already enabled in /etc/pam.d/sudo and warn user 36 | # Check if /etc/pam.d/sudo_local exists, if not create it 37 | # Use vim to insert required text to enable TouchID for sudo into sudo_local 38 | 39 | if grep -q 'pam_tid.so' "/etc/pam.d/sudo"; then 40 | logger -p user.warning -s "Enabling TouchID for sudo in /etc/pam.d/sudo does not persist across updates.\ 41 | See https://support.apple.com/en-us/HT213893" 42 | fi 43 | 44 | if [[ -e "/etc/pam.d/sudo_local" ]]; then 45 | if grep -q 'pam_tid.so' "/etc/pam.d/sudo_local"; then 46 | logger -p user.info -s "TouchID for sudo already enabled" 47 | return 0 48 | fi 49 | 50 | else 51 | 52 | sudo --validate --prompt="[⚠️ ] Password required to create /etc/pam.d/sudo_local: " 53 | 54 | if sudo install -m "444" -g "wheel" -o "root" "/dev/null" "/etc/pam.d/sudo_local"; then 55 | # Use install to create the file /etc/pam.d/sudo_local 56 | # Copies from /dev/null which creates an empty file 57 | # Set permissions to read only, group to wheel and owner to root 58 | logger -p user.info -s "Created /etc/pam.d/sudo_local" 59 | else 60 | logger -p user.error -s "Failed to create /etc/pam.d/sudo_local" 61 | return 1 62 | fi 63 | 64 | if sudo ex -s -c '1i|auth sufficient pam_tid.so' -c x! -c x! "/etc/pam.d/sudo_local"; then 65 | # Invoke Vim in ex mode 66 | # Select line 1, enter insert mode, insert that text write changes and exit 67 | # Need to exit twice to get passed the read only file warning 68 | 69 | logger -p user.info -s "TouchID for sudo enabled" 70 | return 0 71 | else 72 | logger -p user.error -s "Failed to enable TouchID for sudo" 73 | return 1 74 | fi 75 | fi 76 | } 77 | 78 | 79 | function main { 80 | 81 | trap ctrl_c SIGINT 82 | # Detect and react to the user hitting CTRL + C 83 | 84 | enable_touchid_sudo 85 | } 86 | 87 | main "$@" 88 | 89 | -------------------------------------------------------------------------------- /entitlements/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Entitlements 3 | 4 | Collection of scripts for determining which [Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements) macOS (Mach-O) binaries have. 5 | 6 | Inspired by: 7 | * [Cedric Owens](https://twitter.com/cedowens)'s [EntitlementCheck](https://github.com/cedowens/EntitlementCheck) 8 | * [Jonathan Levin](https://twitter.com/morpheus______)'s [OS X/iOS Entitlement Database](http://newosxbook.com/ent.jl) 9 | 10 | For lists of and descriptions of Entitlements see: 11 | * Apple Developer Documentation 12 | * [Bundle Resources > Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements) 13 | * [Security > App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) 14 | 15 | 16 | # Hardened Runtime 17 | 18 | 19 | ## Sources 20 | * [Notarization: the hardened runtime](https://eclecticlight.co/2021/01/07/notarization-the-hardened-runtime/) by [Howard Oakley](https://twitter.com/howardnoakley) 21 | * [The ‘hardened runtime’ explained](https://eclecticlight.co/2019/08/10/the-hardened-runtime-explained/) by [Howard Oakley](https://twitter.com/howardnoakley) 22 | * [Hardened Runtime](https://developer.apple.com/documentation/security/hardened_runtime) by Apple 23 | * [Your Apps and the Future of macOS Security (WWDC2018 Session 702)](https://developer.apple.com/videos/play/wwdc2018/702/) (22:00 mark) by Apple 24 | 25 | # Interesting Entitlements 26 | 27 | 28 | ## `com.apple.private.security.clear-library-validation` 29 | * Formerly `com.apple.security.cs.disable-library-validation` 30 | * Can load arbitrary unsigned plugins/frameworks 31 | * [About com.apple.private.security.clear-library-validation](https://theevilbit.github.io/posts/com.apple.private.security.clear-library-validation/) by [Csaba Fitzl](https://twitter.com/theevilbit) 32 | 33 | 34 | ## `com.apple.security.cs-allow-dyld-environment-variables` 35 | * [Allow DYLD Environment Variables Entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables/) 36 | * Allows injecting Dynamic Libraries (dylib's) via the `DYLD_INSERT_LIBRARIES` environment variable 37 | * [DYLD_INSERT_LIBRARIES DYLIB injection in macOS / OSX](https://theevilbit.github.io/posts/dyld_insert_libraries_dylib_injection_in_macos_osx_deep_dive/) by [Csaba Fitzl](https://twitter.com/theevilbit) 38 | 39 | 40 | ## `com.apple.security.get-task-allow` 41 | * Allows other processes to attach via a debugger 42 | ## `com.apple.security.cs.allow-unsigned-executable-memory` 43 | * [Allow Unsigned Executable Memory Entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-unsigned-executable-memory/) 44 | * Allows overriding or patching C code 45 | * Via `NSCreateObjectFileImageFromMemory` (which is fundamentally insecure) 46 | * Or use the DVDPlayback framework 47 | ## `com.apple.security.files.downlaods.read-only` 48 | * [`com.apple.security.files.downloads.read-only`](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_downloads_read-only/) 49 | * May have access to files the user has selected in an open or save dialog 50 | ## `com.apple.security.files.downloads.read-write` 51 | * May have access to files the user has selected in an open or save dialog 52 | ## `com.apple.security.files.all` 53 | ## `com.apple.security.files.user-selected.read-only` 54 | ## `com.apple.security.files.user-selected.read-write` 55 | ## `com.apple.private.tcc.allow` 56 | * May have TCC access to some protected portions of the OS 57 | 58 | -------------------------------------------------------------------------------- /entitlements/dump-entitlements: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # # macos-scripts/entitlements/dump-entitlements 3 | 4 | # dump-entitlements 5 | 6 | set -uo pipefail 7 | # -o pipefail force pipelines to fail on first non-zero status code 8 | # -u prevent using undefined variables 9 | 10 | IFS=$'\n\t' 11 | # Set Internal Field Separator to newlines and tabs 12 | # This makes bash consider newlines and tabs as separating words 13 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 14 | 15 | 16 | function usage { 17 | echo 18 | echo " Find all binaries on disk, dump their entitlements, uniquely sort the entitlements" 19 | echo " Usage: ./dump-entitlements {find | dump | sort}" 20 | echo 21 | 22 | echo " find Find all binaries on disk and dump them to ${BINARIES_FILENAME}" 23 | echo " dump Process ${BINARIES_FILENAME} and dump the entitlements for each binary to ${ENTITLEMENTS_FILENAME}" 24 | echo " sort Uniquely sort ${ENTITLEMENTS_FILENAME} to ${SORTED_ENTITLEMENTS_FILENAME}" 25 | echo 26 | 27 | exit 0 28 | } 29 | 30 | 31 | ### Utility Functions ### 32 | # ctrl_c 33 | 34 | function ctrl_c { 35 | echo -e "\\n[❌] ${USER} has chosen to quit!" 36 | exit 1 37 | } 38 | 39 | ### END Utility Functions ### 40 | 41 | 42 | function find_binaries { 43 | # find_binaries 44 | 45 | sudo --prompt="[⚠️ ] sudo required to search everywhere" -v 46 | 47 | sudo find / -type f -exec file {} \; -print | grep 'Mach-O' | awk -F ':' '{print $1}' > "${BINARIES_FILENAME}" 48 | } 49 | 50 | 51 | function dump_entitlements { 52 | # dump_entitlements 53 | 54 | while read binary; do 55 | /usr/bin/codesign --display --entitlements - "${binary}" 2>&1 > "${ENTITLEMENTS_FILENAME}" 56 | done < "${BINARIES_FILENAME}" 57 | } 58 | 59 | 60 | function sort_entitlements { 61 | # sort_entitlements 62 | 63 | awk '{print $2}' "${ENTITLEMENTS_FILENAME}" | grep com.apple | sort -u > "${SORTED_ENTITLEMENTS_FILENAME}" 64 | } 65 | 66 | 67 | function main { 68 | 69 | trap ctrl_c SIGINT 70 | # Detect and react to the user hitting CTRL + C 71 | 72 | declare -r ARG=${1:-"usage"} 73 | declare -r DATE=$(date +"%d-%m-%Y") 74 | declare -r BINARIES_FILENAME="Mach-O.txt-${DATE}" 75 | declare -r ENTITLEMENTS_FILENAME="entitlements-${DATE}.txt" 76 | declare -r SORTED_ENTITLEMENTS_FILENAME="sorted-entitlements-${DATE}.txt" 77 | 78 | case "${ARG}" in 79 | 80 | usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯") 81 | usage 82 | ;; 83 | 84 | find|-f|--find) 85 | echo "[⚠️ ] This takes a looong time to run" 86 | echo "Will dump list of binaries to ${BINARIES_FILENAME}" 87 | sleep 3 88 | find_binaries 89 | ;; 90 | 91 | dump|-d|--dump) 92 | dump_entitlements 93 | ;; 94 | 95 | sort|-s|--sort) 96 | sort_entitlements 97 | ;; 98 | 99 | *) 100 | usage 101 | ;; 102 | esac 103 | } 104 | 105 | main "$@" -------------------------------------------------------------------------------- /entitlements/treebeard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/entitlements/treebeard 3 | 4 | # treebeard 5 | 6 | set -uo pipefail 7 | # -o pipefail force pipelines to fail on first non-zero status code 8 | # -u prevent using undefined variables 9 | 10 | IFS=$'\n\t' 11 | # Set Internal Field Separator to newlines and tabs 12 | # This makes bash consider newlines and tabs as separating words 13 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 14 | 15 | 16 | ### Define Colours ### 17 | tput sgr0; 18 | # reset colors 19 | 20 | RESET=$(tput sgr0) 21 | readonly RESET 22 | RED=$(tput setaf 1) 23 | readonly RED 24 | YELLOW=$(tput setaf 3) 25 | readonly YELLOW 26 | GREEN=$(tput setaf 64) 27 | readonly GREEN 28 | ### END Colours ### 29 | 30 | 31 | function usage { 32 | echo 33 | echo " " 34 | echo " Usage: ./treebeard" 35 | echo 36 | 37 | echo " shit some shit" 38 | echo 39 | 40 | exit 0 41 | } 42 | 43 | 44 | ### Utility Functions ### 45 | # ctrl_c 46 | 47 | function ctrl_c { 48 | echo -e "\\n[❌] ${USER} has chosen to quit!" 49 | exit 1 50 | } 51 | 52 | ### END Utility Functions ### 53 | 54 | function get_binaries { 55 | # get_binaries 56 | 57 | for binary in /Applications/*.app/Contents/MacOS/* \ 58 | /usr/bin/* \ 59 | /usr/local/bin/* \ 60 | /usr/sbin/*; do 61 | 62 | if [[ "${binary}" =~ Xcode.app ]]; then 63 | continue 64 | # Skip Xcode because it takes ages to run codesign on it 65 | elif [[ "${binary}" =~ Safari.app ]]; then 66 | continue 67 | # Skip Safari because it is a system application 68 | # TODO: Skip other systems apps like Pages? 69 | fi 70 | 71 | binaries+=("${binary}"); 72 | 73 | done 74 | } 75 | 76 | 77 | function check_hardened_runtime { 78 | # check_hardened_runtime 79 | # 80 | 81 | declare -r binary_to_check=${1:?binary_to_check not passed to check_hardened_runtime} 82 | 83 | if codesign --display --verbose "${binary_to_check}" 2>&1 | awk 'FNR == 4 {print $4}' | grep --quiet 'runtime'; then 84 | # awk: print 4th column of 4th line 85 | # e.g. flags=0x0(none), flags=0x10000(runtime), flags=0x12000(library-validation,runtime) 86 | # For bitmask values see: 87 | # https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/osfmk/kern/cs_blobs.h 88 | return 0 89 | else 90 | return 1 91 | fi 92 | } 93 | 94 | 95 | function check_entitlements { 96 | # check_entitlements 97 | 98 | declare -r binary=${1:?binary_to_check not passed to check_entitlements} 99 | 100 | declare -a entitlements 101 | entitlements=(com.apple.security.cs.allow-dyld-environment-variables\ 102 | com.apple.security.cs.disable-library-validation\ 103 | com.apple.private.security.clear-library-validation\ 104 | com.apple.security.get-task-allow\ 105 | com.apple.security.cs.allow-unsigned-executable-memory\ 106 | com.apple.security.files.downloads.read-only\ 107 | com.apple.security.files.all\ 108 | com.apple.security.files.user-selected.read-only\ 109 | com.apple.private.security.clear-library-validation\ 110 | com.apple.private.tcc.allow) 111 | 112 | codesign_entitlements_output=$(/usr/bin/codesign --display --entitlements - "${binary}" 2>&1) 113 | current_binary="${binary}" 114 | 115 | for entitlement in "${entitlements[@]}"; do 116 | 117 | if echo "${codesign_entitlements_output}" | grep --quiet "${entitlement}"; then 118 | 119 | if [[ "${current_binary}" = "${binary}" ]]; then 120 | 121 | echo 122 | if check_hardened_runtime "${binary}"; then 123 | echo "${binary} (${GREEN}Hardened Runtime${RESET})" 124 | else 125 | echo "${binary}" 126 | fi 127 | 128 | current_binary="" 129 | fi 130 | echo "${entitlement}" 131 | fi 132 | done 133 | } 134 | 135 | 136 | function main { 137 | 138 | trap ctrl_c SIGINT 139 | # Detect and react to the user hitting CTRL + C 140 | 141 | declare -a binaries 142 | 143 | get_binaries 144 | 145 | for binary in "${binaries[@]}"; do 146 | check_entitlements "${binary}" 147 | done 148 | 149 | } 150 | 151 | main "$@" 152 | -------------------------------------------------------------------------------- /legacy-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/legacy-check 3 | 4 | # legacy-check 5 | # Check for 32-bit applications 6 | # Check for loaded and on disk third party kernel extensions 7 | 8 | set -euo pipefail 9 | # -e exit if any command returns non-zero status code 10 | # -u prevent using undefined variables 11 | # -o pipefail force pipelines to fail on first non-zero status code 12 | 13 | 14 | IFS=$'\n\t' 15 | # Set Internal Field Separator to newlines and tabs 16 | # This makes bash consider newlines and tabs as separating words 17 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 18 | 19 | 20 | function get_x86_applications { 21 | # get_x86_applications 22 | # Can also use: 23 | # mdfind "kMDItemExecutableArchitectures == '*i386*' && kMDItemExecutableArchitectures != '*x86*'" 24 | # Via @rtrouton https://twitter.com/rtrouton/status/1130482310762115072 25 | # See also: https://eclecticlight.co/2019/05/22/how-to-find-all-your-32-bit-apps-a-non-buyers-guide/ 26 | 27 | while IFS=$'\n' read -r app; do 28 | x86_apps+=("${app}"); 29 | done < <(system_profiler SPApplicationsDataType \ 30 | | grep -E '^ .*:$|64-Bit \(Intel\):|Location' \ 31 | | grep -A1 ': No' \ 32 | | grep 'Location' \ 33 | | sed -e 's/.*Location: //' ) 34 | # Massive thanks to @MacLemon for this oneliner 35 | } 36 | 37 | 38 | function get_kexts { 39 | # get_kexts 40 | # Get list of loaded non apple kexts via kmutil 41 | # Could also use kextstat but that calls kmutil 42 | # Get list of third party kexts in /Library/Extensions/ 43 | 44 | while IFS=$'\n' read -r app; do 45 | loaded_kernel_extensions+=("${kext}"); 46 | done < <(kmutil showloaded --show loaded --variant-suffix release \ 47 | | grep -v 'com.apple' \ 48 | | tail -n +2 \ 49 | | head -n 3 \ 50 | | awk '{print $6}') 51 | 52 | on_disk_kernel_extensions=(/Library/Extensions/*) 53 | } 54 | 55 | 56 | function main { 57 | 58 | declare -a x86_apps 59 | declare -a loaded_kernel_extensions 60 | declare -a on_disk_kernel_extensions 61 | 62 | 63 | get_x86_applications 64 | 65 | if ! [[ ${#x86_apps[@]} -eq 0 ]]; then 66 | echo "32-bit applications:" 67 | 68 | for app in "${x86_apps[@]}"; do 69 | echo " ${app}" 70 | done 71 | else 72 | echo "No 32-bit applications found" 73 | fi 74 | 75 | 76 | get_kexts 77 | 78 | if ! [[ ${#loaded_kernel_extensions[@]} -eq 0 ]]; then 79 | echo "Loaded third party Kernel Extensions:" 80 | 81 | for kext in "${loaded_kernel_extensions[@]}"; do 82 | echo " ${kext}" 83 | done 84 | else 85 | echo "No loaded third party Kernel Extensions found" 86 | fi 87 | 88 | if ! [[ ${#on_disk_kernel_extensions[@]} -eq 0 ]]; then 89 | echo "On disk third party Kernel Extensions:" 90 | 91 | for kext in "${on_disk_kernel_extensions[@]}"; do 92 | echo " ${kext}" 93 | done 94 | else 95 | echo "No on disk third party Kernel Extensions found" 96 | fi 97 | } 98 | 99 | main "$@" 100 | -------------------------------------------------------------------------------- /list-apps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/list-apps 3 | 4 | # list-apps 5 | # List installed applications 6 | # Explitict goal of not using system_profiler 7 | 8 | set -euo pipefail 9 | # -e exit if any command returns non-zero status code 10 | # -u prevent using undefined variables 11 | # -o pipefail force pipelines to fail on first non-zero status code 12 | 13 | IFS=$'\n\t' 14 | # Set Internal Field Separator to newlines and tabs 15 | # This makes bash consider newlines and tabs as separating words 16 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 17 | 18 | ### UTILITY FUNCTIONS ### 19 | # ctrl_c 20 | # usage 21 | 22 | function ctrl_c { 23 | echo -e "\\n[❌] ${USER} has chosen to quit!" 24 | exit 1 25 | } 26 | 27 | ### END UTILITY FUNCTIONS ### 28 | 29 | 30 | function main { 31 | 32 | trap ctrl_c SIGINT 33 | # Detect and react to the user hitting CTRL + C 34 | 35 | declare -a DIRECTORIES=("/Applications" "$HOME/Applications") 36 | readonly DIRECTORIES 37 | 38 | for directory in "${DIRECTORIES[@]}"; do 39 | find "${directory}" -type d -name "*.app" -depth 1 -exec basename {} .app \; 40 | done 41 | 42 | } 43 | 44 | main "$@" 45 | -------------------------------------------------------------------------------- /office-macro-security: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/office-macro-security 3 | 4 | # office-macro-security 5 | # Disable macro execution in MS Office 6 | # Restrict macro access to the system 7 | # https://macadmins.software/docs/VBSecurityControls.pdf 8 | 9 | set -uo pipefail 10 | # -u prevent using undefined variables 11 | # -o pipefail force pipelines to fail on first non-zero status code 12 | 13 | IFS=$'\n\t' 14 | # Set Internal Field Separator to newlines and tabs 15 | # This makes bash consider newlines and tabs as separating words 16 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 17 | 18 | ### UTILITY FUNCTIONS ### 19 | # ctrl_c 20 | # usage 21 | # has_office 22 | # restrict_message 23 | 24 | function ctrl_c { 25 | echo -e "\\n[❌] ${USER} has chosen to quit!" 26 | exit 1 27 | } 28 | 29 | 30 | function usage { 31 | echo -e "\\n Disable or restrict macro execution in Office" 32 | echo -e " Usage: ./disable-office-macros {list | disable | restrict | both | undo}\\n" 33 | 34 | echo " list List current settings" 35 | echo " disable Entirely disable macro execution" 36 | echo " restrict Disable access to dylibs, system(). popen() & AppleScript" 37 | echo " both Disable execution & restrict system access" 38 | echo " undo Restore all settings to default value" 39 | 40 | exit 0 41 | } 42 | 43 | 44 | function has_office { 45 | # has_office 46 | # Will exit 1 if none of the installed office apps 47 | # have been launched before 48 | 49 | if ! defaults read com.microsoft.office >/dev/null 2>&1; then 50 | echo "[] Office not installed on ${HOST}" 51 | exit 1 52 | fi 53 | } 54 | 55 | 56 | function restrict_message { 57 | echo "[] Restricting macro access to system..." 58 | echo "[] Disabling ability to use:" 59 | echo "[-] external dylibs" 60 | echo "[-] system()" 61 | echo "[-] popen()" 62 | echo "[-] MacScript4() (AppleScript)" 63 | echo "[-] Modify the VB project" 64 | } 65 | 66 | 67 | ### END UTILITY FUNCTIONS ### 68 | 69 | function list_preferences { 70 | 71 | # List all values for the affected keys 72 | 73 | for preference_key in "${preference_keys[@]}"; do 74 | if key_value=$(defaults read com.microsoft.office "${preference_key}" 2>/dev/null); then 75 | echo "${preference_key} ${key_value}" 76 | else 77 | echo "${preference_key} not explicitly set" 78 | fi 79 | done 80 | } 81 | 82 | function disable_macros { 83 | 84 | # VisualBasicMacroExecutionState 85 | # Controls whether macros are ever allowed to execute and what 86 | # the user experience is when opening a file that contains a macro 87 | # Office Version(s): 2016 & 2019 88 | # Default: DisabledWithWarnings 89 | # Other Values: 90 | # DisabledWithoutWarnings 91 | # EnabledWithoutWarnings 92 | defaults write com.microsoft.office VisualBasicMacroExecutionState -string DisabledWithoutWarnings 93 | } 94 | 95 | function restrict_macro_execution { 96 | 97 | # DisableVisualBasicExternalDylibs 98 | # Controls if macros are allowed to use DECLARE2 to bind symbol name 99 | # to an external procedure in the local OS 100 | # Office Version(s): 2016 & 2019 101 | # Default: false 102 | # Other Values: 103 | # true 104 | defaults write com.microsoft.office DisableVisualBasicExternalDylibs -bool true 105 | 106 | 107 | # AllowVisualBasicToBindToSystem 108 | # Controls if macros are allowed to use a DECLARE to bind to the system() OS API 109 | # This API allows macros to execute arbitrary commands 110 | # Office Version(s): 2016 & 2019 111 | # Default: true 112 | # Other Values: 113 | # false 114 | defaults write com.microsoft.office AllowVisualBasicToBindToSystem -bool false 115 | 116 | 117 | # DisableVisualBasicToBindToPopen 118 | # Controls if macros are allowed to use a DECLARE to bind to the popen() OS API 119 | # This API allows macros to execute arbitrary commands 120 | # Office Version(s): 2016 & 2019 121 | # Default: false 122 | # Other Values: 123 | # true 124 | defaults write com.microsoft.office DisableVisualBasicToBindToPopen -bool true 125 | 126 | 127 | # DisableVisualBasicMacScript 128 | # Controls if macros are allowed to invoke the MacScript4() Visual Basic API 129 | # This API allows macros to execute arbitrary code via AppleScript 130 | # Office Version(s): 2016 & 2019 131 | # Default: false 132 | # Other Values: 133 | # true 134 | defaults write com.microsoft.office DisableVisualBasicMacScript -bool true 135 | 136 | 137 | # VBAObjectModelIsTrusted 138 | # Controls if macros are allowed to modify the VB project 139 | # itself through the VBA object model 140 | # Office Version(s): 2019 141 | # Default: true 142 | # Other Values: 143 | # false 144 | defaults write com.microsoft.office VBAObjectModelIsTrusted -bool false 145 | } 146 | 147 | function restore_to_defaults { 148 | defaults write com.microsoft.office VisualBasicMacroExecutionState -string DisabledWithWarnings 149 | defaults write com.microsoft.office DisableVisualBasicExternalDylibs -bool false 150 | defaults write com.microsoft.office AllowVisualBasicToBindToSystem -bool true 151 | defaults write com.microsoft.office DisableVisualBasicToBindToPopen -bool false 152 | defaults write com.microsoft.office DisableVisualBasicMacScript -bool false 153 | 154 | # Office 2019 only 155 | defaults write com.microsoft.office VBAObjectModelIsTrusted -bool true 156 | } 157 | 158 | function main { 159 | 160 | has_office 161 | 162 | declare -r arg=${1:-"usage"} 163 | declare -a preference_keys 164 | preference_keys=(VisualBasicMacroExecutionState DisableVisualBasicExternalDylibs\ 165 | AllowVisualBasicToBindToSystem DisableVisualBasicToBindToPopen\ 166 | DisableVisualBasicMacScript VBAObjectModelIsTrusted) 167 | 168 | case "${arg}" in 169 | 170 | usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯") 171 | usage 172 | ;; 173 | 174 | list|-l|--list) 175 | list_preferences 176 | ;; 177 | 178 | disable|-d|--disable) 179 | echo "[] Disabling macro execution" 180 | disable_macros 181 | echo "[-] Disabled" 182 | ;; 183 | 184 | restrict|-r|--restrict) 185 | restrict_message 186 | restrict_macro_execution 187 | ;; 188 | 189 | both|-b|--both) 190 | echo "[] Disabling macro execution" 191 | disable_macros 192 | echo -e "[-] Disabled\n" 193 | 194 | restrict_message 195 | restrict_macro_execution 196 | ;; 197 | 198 | undo|-u|--undo) 199 | echo "[] Restoring default settings" 200 | restore_to_defaults 201 | echo "[-] Restored" 202 | ;; 203 | 204 | *) 205 | usage 206 | ;; 207 | 208 | esac 209 | } 210 | 211 | main "$@" 212 | -------------------------------------------------------------------------------- /pkg-expand: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/pkg-expand 3 | 4 | # pkg-expand 5 | # Expands a package serches for embeded packes and expands them 6 | # Searches for Bill of Material (Bom) files and reads them 7 | 8 | set -uo pipefail 9 | # -u prevent using undefined variables 10 | # -o pipefail force pipelines to fail on first non-zero status code 11 | 12 | IFS=$'\n\t' 13 | # Set Internal Field Separator to newlines and tabs 14 | # This makes bash consider newlines and tabs as separating words 15 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 16 | 17 | function usage { 18 | 19 | echo -e "\\n Expands a package and any packages inside it" 20 | echo " ./pkg-expand {.pkg}" 21 | 22 | exit 0 23 | } 24 | 25 | 26 | function pkg_search { 27 | 28 | while IFS=$'\n' read -r package; do 29 | packages+=("${package}"); 30 | done < <(find "${expand_dir}" -name "*.pkg" -type f) 31 | } 32 | 33 | 34 | function unpack { 35 | 36 | local embedded_pkg_name 37 | declare -i counter=0 38 | 39 | for package in "${packages[@]}"; do 40 | embedded_pkg_name="$(basename "${package}" | awk -F '.pkg' '{print $1}')" 41 | if [[ "${embedded_pkg_name}" == "${pkg_name}" ]]; then 42 | embedded_pkg_name="${embedded_pkg_name}-${counter}" 43 | counter=+1 44 | fi 45 | pkgutil --expand-full "${package}" "${expand_dir}/${embedded_pkg_name}" 46 | done 47 | } 48 | 49 | 50 | function bom_search { 51 | while IFS=$'\n' read -r bom; do 52 | boms+=("${bom}"); 53 | done < <(find "${expand_dir}" -name "Bom" -type f) 54 | } 55 | 56 | 57 | function bom_read { 58 | local pkg_name 59 | 60 | for bom in "${boms[@]}"; do 61 | pkg_name=$(echo "${bom}" | awk -F '/' '{print $(NF-1)}' | tr -d '.pkg') 62 | lsbom "${bom}" > "${expand_dir}/${pkg_name}-Bom" 63 | done 64 | 65 | } 66 | 67 | 68 | function find_preinstall { 69 | while IFS=$'\n' read -r preinstall; do 70 | preinstall_scripts+=("${preinstall}"); 71 | done < <(find "${expand_dir}" -name 'preinstall' -type f) 72 | } 73 | 74 | function check_preinstall { 75 | 76 | for script in "${preinstall_scripts[@]}"; do 77 | echo "Checking ${script}" 78 | grep -ni "cp /tmp" "${script}" 79 | grep -ni "chmod 777 /Applications" "${script}" 80 | grep -ni "chown root:admin /Applications/" "${script}" 81 | done 82 | } 83 | 84 | function find_postinstall { 85 | while IFS=$'\n' read -r postinstall; do 86 | postinstall_scripts+=("${postinstall}"); 87 | done < <(find "${expand_dir}" -name 'postinstall' -type f) 88 | } 89 | 90 | function check_postinstall { 91 | 92 | for script in "${postinstall_scripts[@]}"; do 93 | echo "Checking ${script}" 94 | grep -ni "cp /tmp" "${script}" 95 | grep -ni "chmod 777 /Applications" "${script}" 96 | grep -ni "chown root:admin /Applications/" "${script}" 97 | done 98 | } 99 | 100 | 101 | function main { 102 | 103 | declare -a packages 104 | declare -a boms 105 | declare -a preinstall_scripts 106 | declare -a postinstall_scripts 107 | 108 | arg=${1:-"usage"} 109 | 110 | if [[ "${arg}" =~ ^(usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯")$ ]]; then 111 | usage 112 | fi 113 | 114 | pkg_name=$(basename "${arg}" | awk -F '.pkg' '{print $1}') 115 | readonly expand_dir="/tmp/${pkg_name}" 116 | mkdir "${expand_dir}" 117 | pkgutil --expand-full "${arg}" "${expand_dir}/${pkg_name}" 118 | 119 | pkg_search 120 | unpack 121 | bom_search 122 | bom_read 123 | 124 | find_preinstall 125 | find_postinstall 126 | check_preinstall 127 | check_postinstall 128 | } 129 | 130 | main "$@" 131 | 132 | -------------------------------------------------------------------------------- /quarantine_score: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/quarantine_score 3 | 4 | # quarantine_score 5 | # For files with the com.apple.quarantine extended attribute 6 | # Print the name of the file and the quarantine flag value 7 | 8 | 9 | set -euo pipefail 10 | # -e exit if any command returns non-zero status code 11 | # -u prevent using undefined variables 12 | # -o pipefail force pipelines to fail on first non-zero status code 13 | 14 | IFS=$'\n\t' 15 | # Set Internal Field Separator to newlines and tabs 16 | # This makes bash consider newlines and tabs as separating words 17 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 18 | 19 | 20 | ### Define Colours ### 21 | tput sgr0; 22 | # reset colors 23 | 24 | RED="$(tput setaf 1)" 25 | readonly RED 26 | RESET="$(tput sgr0)" 27 | readonly RESET 28 | 29 | ### END Colours ### 30 | 31 | 32 | ### UTILITY FUNCTIONS ### 33 | # ctrl_c 34 | 35 | 36 | function ctrl_c { 37 | echo -e "\\n[❌] ${USER} has chosen to quit!" 38 | exit 1 39 | } 40 | 41 | 42 | ### END UTILITY FUNCTIONS ### 43 | 44 | 45 | function usage { 46 | 47 | echo -e "\\nPrint gatekeeper score if file has the com.apple.quarantine extended attribute. Highlights scores other than '0083'" 48 | echo " ./quarantine_score {directory_path}" 49 | echo -e " {directory_path} defaults to $HOME/Downloads\\n" 50 | 51 | exit 0 52 | } 53 | 54 | 55 | function get_files { 56 | 57 | while IFS=$'\n' read -r file; do 58 | files+=("${file}"); 59 | done < <(find "${arg}" -type f -maxdepth 1) 60 | } 61 | 62 | 63 | function procees_files { 64 | 65 | for file in "${files[@]}"; do 66 | if xattr "${file}" | grep -q 'quarantine'; then 67 | flag=$(xattr -p "com.apple.quarantine" "${file}" | awk -F ';' '{print $1}') 68 | 69 | if [ "${flag}" != "0083" ]; then 70 | echo -e "File: ${file}\\nFlag: ${RED}${flag}${RESET}" 71 | else 72 | file_name=$(basename "${file}") 73 | echo -e "File: ${file_name}\\nFlag: ${flag}" 74 | fi 75 | fi 76 | done 77 | } 78 | 79 | 80 | function main { 81 | 82 | trap ctrl_c SIGINT 83 | # Detect and react to the user hitting CTRL + C 84 | 85 | declare -a files 86 | 87 | arg=${1:-"$HOME/Downloads"} 88 | 89 | case "${arg}" in 90 | usage|help|-h|--help|🤷‍♂️|🤷‍♀️|"¯\_(ツ)_/¯") 91 | usage 92 | ;; 93 | 94 | *) 95 | if [[ -d "${arg}" ]]; then 96 | get_files 97 | procees_files 98 | else 99 | echo "[❌] ${arg} is not a directory" 100 | exit 1 101 | fi 102 | ;; 103 | esac 104 | } 105 | 106 | main "$@" 107 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # macos-scripts/test 3 | 4 | # test 5 | # Find shell scripts and run shellcheck on them 6 | # Adapted from jessfraz/dotfiles/bin/test.sh 7 | # https://github.com/jessfraz/dotfiles/blob/master/test.sh 8 | 9 | set -euo pipefail 10 | # -e exit if any command returns non-zero status code 11 | # -u prevent using undefined variables 12 | # -o pipefail force pipelines to fail on first non-zero status code 13 | 14 | /usr/bin/tput sgr0; 15 | # reset colors 16 | 17 | 18 | function check_shellcheck { 19 | # check_shellcheck 20 | # Check if shellcheck is installed 21 | 22 | if ! [ -x "$(command -v shellcheck)" ]; then 23 | echo "[FAIL] shellcheck not installed" 24 | echo "[INFO] macOS: brew install shellcheck" 25 | echo "[INFO] Linux: apt install shellcheck" 26 | exit 1 27 | fi 28 | } 29 | 30 | 31 | function find_files { 32 | # find_files 33 | # Find all regular files in source directory 34 | 35 | while IFS=$'\n' read -r file; do 36 | FILES+=("${file}"); 37 | done < <(/usr/bin/find . -maxdepth 1 -type f \ 38 | -not -iwholename '*.git*' \ 39 | -not -iwholename '*.tar.xz' \ 40 | | /usr/bin/sort -u) 41 | } 42 | 43 | 44 | function analyse_shell_scripts { 45 | # analyse_shell_scripts 46 | # Iterate over $FILES to find bash and zsh scripts 47 | # Call shellcheck on them via analyse function 48 | 49 | for file in "${FILES[@]}"; do 50 | if /usr/bin/file "${file}" | /usr/bin/grep --quiet "shell" || \ 51 | /usr/bin/file "${file}" | /usr/bin/grep --quiet "bash" ; then 52 | # Find bash scripts 53 | # Running file on a script with the shebang "#!/usr/bin/env bash" returns 54 | # "a /usr/bin/env bash script, ASCII text executable" 55 | # Versus a script with the shebang "#!/bin/bash" which returns 56 | # "Bourne-Again shell script, ASCII text executable" 57 | analyse "${file}" 58 | 59 | elif /usr/bin/file "${file}" | /usr/bin/grep --quiet "zsh"; then 60 | # Find zsh scripts 61 | # Running file on a script with shebang "#!/usr/bin/env zsh" returns 62 | # "a /usr/bin/env zsh script text executable" 63 | analyse "${file}" 64 | fi 65 | done 66 | } 67 | 68 | 69 | function analyse { 70 | # analyse 71 | # Wrapper for shellcheck to handle errors 72 | # Always invokes shellcheck in bash mode 73 | # as shellcheck does not support zsh 74 | 75 | local shell_file=${1:?shell_file not passed to lint_shell_file} 76 | 77 | if shellcheck --shell=bash "${shell_file}" ; then 78 | # Run shellcheck on the file 79 | # Always uses bash mode as bittersweet now has a zsh shebang 80 | # Shelllcheck doesn't support zsh. 81 | echo "[PASS] $(/usr/bin/basename "${shell_file}")" 82 | else 83 | echo "[FAIL] $(/usr/bin/basename "${shell_file}")" 84 | ERRORS+=("${shell_file}") 85 | # If shellcheck fails add failing file name to array 86 | fi 87 | } 88 | 89 | 90 | function main { 91 | 92 | check_shellcheck 93 | 94 | declare -a ERRORS 95 | declare -a FILES 96 | 97 | find_files 98 | analyse_shell_scripts 99 | 100 | 101 | if [[ ${#ERRORS[@]} -eq 0 ]]; then 102 | # If ERRORS empty then 103 | echo "[PASS] No errors, hooray" 104 | exit 0 105 | else 106 | # If ERRORS not empty, print the names of files which failed 107 | echo "[FAIL] These files failed linting: ${ERRORS[*]}" 108 | exit 1 109 | fi 110 | } 111 | 112 | main "$@" 113 | -------------------------------------------------------------------------------- /uuid: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # macos-scripts/uuid 3 | 4 | # uuid 5 | # Print paths and files which potentially contain the UUID (IOPlatformUUID) 6 | # Instead of executing something like ioreg -d2 -c IOPlatformExpertDevice | awk -F\" '/IOPlatformUUID/{print $(NF-1)}' 7 | 8 | set -uo pipefail 9 | # -u prevent using undefined variables 10 | # -o pipefail force pipelines to fail on first non-zero status code 11 | setopt null_glob 12 | # Make any glob that doesn't match expand to nothing 13 | # prevents errors when trying to iterate over non-existent files 14 | 15 | function main { 16 | 17 | declare -a UUID_PATHS=("${HOME}/Library/Application Support/CrashReporter/" \ 18 | "${HOME}/Library/Dictionaries/CoreDataUbiquitySupport/" \ 19 | "${HOME}/Library/Containers/*/Data/Library/Application Support/CrashReporter/") 20 | # $HOME/Library/Keychains/$UUID/ 21 | # Inside $HOME/Library/Keychains/ there will be a directory named after the UUID of the system 22 | # $HOME/Library/Preferences/ByHost/com.apple.loginwindow.UUID.plist 23 | 24 | declare -a UUID_FILES=("$HOME/Pictures/Photos Library.photoslibrary/database/Photos.sqlite.lock") 25 | 26 | echo "Paths which potentially contain UUID:" 27 | for UUID_PATH in "${UUID_PATHS[@]}"; do 28 | echo "${UUID_PATH}"* 29 | done 30 | 31 | echo 32 | echo "Files which potentially contain UUID:" 33 | for UUID_FILE in "${UUID_FILES[@]}"; do 34 | echo "${UUID_FILE}" 35 | # cat "${UUID_FILE}" | grep -oE '[[:alnum:]]{8}(-[[:alnum:]]{4}){3}-[[:alnum:]]{12}' 36 | done 37 | 38 | UUID=$(echo "$HOME/Library/Application Support/CrashReporter/"* | grep -oE '[[:alnum:]]{8}(-[[:alnum:]]{4}){3}-[[:alnum:]]{12}' | head -n 1) 39 | echo 40 | echo "UUID is probably: ${UUID}" 41 | echo 42 | 43 | echo "Other files which might contain the UUID:" 44 | mdfind "${UUID}" | grep -v '/Library/Application\ Support/' | grep -v 'CoreDataUbiquitySupport' | grep -v 'Photos.sqlite.lock' 45 | 46 | } 47 | 48 | main "$@" 49 | --------------------------------------------------------------------------------