tcc-audit.sh 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #!/usr/bin/env bash
  2. # mac-ops :: tcc-audit.sh
  3. # Read the TCC (Transparency, Consent, Control) databases to surface which
  4. # apps have which permissions, what's been denied, and where to fix it.
  5. #
  6. # TCC databases:
  7. # ~/Library/Application Support/com.apple.TCC/TCC.db (user-scope)
  8. # /Library/Application Support/com.apple.TCC/TCC.db (system-scope, requires sudo)
  9. #
  10. # The user DB is readable in some macOS releases under SIP/FDA assumptions;
  11. # this script gracefully degrades when access is denied.
  12. set -u
  13. APP_FILTER=""
  14. SERVICE_FILTER=""
  15. SHOW_DENIED_ONLY=0
  16. while [[ $# -gt 0 ]]; do
  17. case "$1" in
  18. -a|--app) APP_FILTER="$2"; shift 2 ;;
  19. -s|--service) SERVICE_FILTER="$2"; shift 2 ;;
  20. --denied) SHOW_DENIED_ONLY=1; shift ;;
  21. --help|-h)
  22. cat <<EOF
  23. Usage: $0 [options]
  24. -a, --app PATTERN Filter by bundle ID or name (e.g. -a slack, -a com.slack.*)
  25. -s, --service PATTERN Filter by TCC service (e.g. -s ScreenCapture, -s Camera)
  26. --denied Show only denied grants (the most common "broken" cause)
  27. --json, --redact, --quiet, --verbose
  28. Examples:
  29. $0 # all grants on this user
  30. $0 --denied # what apps were denied something
  31. $0 -a Slack # Slack's permission state
  32. $0 -s ScreenCapture # who has Screen Recording
  33. Service catalog (most common):
  34. kTCCServiceScreenCapture Screen Recording
  35. kTCCServiceMicrophone Microphone
  36. kTCCServiceCamera Camera
  37. kTCCServiceAccessibility Accessibility (control your Mac)
  38. kTCCServiceSystemPolicyAllFiles Full Disk Access
  39. kTCCServicePostEvent Synthetic input events
  40. kTCCServiceListenEvent Input event listening
  41. kTCCServiceAppleEvents Automation (controlling other apps)
  42. kTCCServicePhotos Photos library
  43. kTCCServiceContactsFull Contacts
  44. kTCCServiceCalendar Calendars
  45. kTCCServiceReminders Reminders
  46. If a script-controlled app has lost permission, the typical fix is:
  47. System Settings → Privacy & Security → <Service> → toggle the app off, then on
  48. or:
  49. tccutil reset <Service> <bundle-id> (resets to "Ask again" — re-prompts user)
  50. Read references/tcc-mechanics.md for the deep dive.
  51. EOF
  52. exit 0 ;;
  53. *) shift ;;
  54. esac
  55. done
  56. source "$(dirname "$0")/_lib/common.sh"
  57. parse_common_flags "$@"
  58. maybe_filter_self "$@"
  59. user_tcc="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
  60. sys_tcc="/Library/Application Support/com.apple.TCC/TCC.db"
  61. # ----------------------------------------------------------------------------
  62. section "1. TCC.db ACCESSIBILITY CHECK"
  63. # ----------------------------------------------------------------------------
  64. if [[ -r "$user_tcc" ]]; then
  65. log_pass "User TCC.db readable" "$user_tcc"
  66. user_readable=1
  67. else
  68. log_warn "User TCC.db readable" "no (this terminal needs Full Disk Access)"
  69. user_readable=0
  70. fi
  71. if [[ -r "$sys_tcc" ]]; then
  72. log_pass "System TCC.db readable" "$sys_tcc"
  73. sys_readable=1
  74. elif sudo -n true 2>/dev/null; then
  75. if sudo -n test -r "$sys_tcc"; then
  76. log_info "System TCC.db" "readable via sudo (cached credential)"
  77. sys_readable=1
  78. else
  79. log_info "System TCC.db" "would need sudo"
  80. sys_readable=0
  81. fi
  82. else
  83. log_info "System TCC.db" "requires sudo (skipped)"
  84. sys_readable=0
  85. fi
  86. if [[ "$user_readable" -eq 0 ]] && [[ "$sys_readable" -eq 0 ]]; then
  87. note ""
  88. note " Neither TCC.db is readable from this terminal."
  89. note " To grant Full Disk Access to your terminal:"
  90. note " System Settings → Privacy & Security → Full Disk Access → +"
  91. note " Add: /Applications/Utilities/Terminal.app (or your terminal app)"
  92. note " Then restart the terminal session."
  93. emit_summary
  94. exit 0
  95. fi
  96. # ----------------------------------------------------------------------------
  97. section "2. PERMISSION GRANTS"
  98. # ----------------------------------------------------------------------------
  99. # auth_value semantics:
  100. # 0 = Denied
  101. # 1 = Unknown
  102. # 2 = Allowed
  103. # 3 = Limited (e.g. partial Photos access)
  104. # The 'service' column is kTCC* string; 'client' is bundle ID; 'client_type' is 0=bundle, 1=path
  105. # Modern TCC.db schemas have additional columns; we select defensively.
  106. build_filter() {
  107. local where="1=1"
  108. [[ -n "$APP_FILTER" ]] && where="$where AND (client LIKE '%${APP_FILTER//\'/}%' COLLATE NOCASE)"
  109. [[ -n "$SERVICE_FILTER" ]] && where="$where AND (service LIKE '%${SERVICE_FILTER//\'/}%' COLLATE NOCASE)"
  110. [[ "$SHOW_DENIED_ONLY" -eq 1 ]] && where="$where AND auth_value = 0"
  111. echo "$where"
  112. }
  113. query_tcc() {
  114. local db="$1"
  115. local where
  116. where=$(build_filter)
  117. sqlite3 -separator '|' "$db" \
  118. "SELECT service, client, auth_value, datetime(last_modified, 'unixepoch') FROM access WHERE $where ORDER BY auth_value, service, client" \
  119. 2>/dev/null
  120. }
  121. if [[ "$user_readable" -eq 1 ]]; then
  122. note " --- User-scope (per-user permission grants) ---"
  123. rows=$(query_tcc "$user_tcc")
  124. if [[ -z "$rows" ]]; then
  125. log_pass "User TCC grants matching filter" "0 rows"
  126. else
  127. count=$(echo "$rows" | wc -l | tr -d ' ')
  128. log_info "User TCC grants" "$count rows"
  129. note " service | client | auth | last modified"
  130. note " -----------------------------|----------------------------------------------------|------|------------------------"
  131. echo "$rows" | head -50 | awk -F'|' '{
  132. svc = substr($1, 1, 28)
  133. cli = substr($2, 1, 50)
  134. auth = $3
  135. ts = $4
  136. label = (auth == 0 ? "DENY" : (auth == 2 ? "ALLOW" : (auth == 3 ? "LIM" : "?")))
  137. printf " %-28s | %-50s | %-4s | %s\n", svc, cli, label, ts
  138. }'
  139. denied=$(echo "$rows" | awk -F'|' '$3 == 0' | wc -l | tr -d ' ')
  140. if [[ "$denied" -gt 0 ]]; then
  141. log_warn "User TCC denials" "$denied — see DENY rows above"
  142. fi
  143. fi
  144. fi
  145. if [[ "$sys_readable" -eq 1 ]]; then
  146. note ""
  147. note " --- System-scope (machine-wide grants, e.g. Full Disk Access) ---"
  148. if [[ -r "$sys_tcc" ]]; then
  149. rows=$(query_tcc "$sys_tcc")
  150. else
  151. rows=$(sudo sqlite3 -separator '|' "$sys_tcc" \
  152. "SELECT service, client, auth_value, datetime(last_modified, 'unixepoch') FROM access WHERE $(build_filter) ORDER BY auth_value, service, client" 2>/dev/null)
  153. fi
  154. if [[ -z "$rows" ]]; then
  155. log_pass "System TCC grants matching filter" "0 rows"
  156. else
  157. count=$(echo "$rows" | wc -l | tr -d ' ')
  158. log_info "System TCC grants" "$count rows"
  159. echo "$rows" | head -30 | awk -F'|' '{
  160. svc = substr($1, 1, 28)
  161. cli = substr($2, 1, 50)
  162. auth = $3
  163. ts = $4
  164. label = (auth == 0 ? "DENY" : (auth == 2 ? "ALLOW" : (auth == 3 ? "LIM" : "?")))
  165. printf " %-28s | %-50s | %-4s | %s\n", svc, cli, label, ts
  166. }'
  167. fi
  168. fi
  169. # ----------------------------------------------------------------------------
  170. section "3. RECENT TCC PROMPTS"
  171. # ----------------------------------------------------------------------------
  172. # Look for tccd / system extension prompt activity in the last 7 days
  173. prompts=$(log show --last 7d --style compact \
  174. --predicate 'process == "tccd"' 2>/dev/null \
  175. | grep -iE "(prompt|denied|auth)" | head -10)
  176. if [[ -n "$prompts" ]]; then
  177. log_info "Recent tccd activity (7d)" "see below"
  178. echo "$prompts" | sed 's/^/ /'
  179. else
  180. log_pass "Recent tccd activity" "quiet"
  181. fi
  182. # ----------------------------------------------------------------------------
  183. emit_summary
  184. if [[ "$JSON_MODE" -eq 0 ]]; then
  185. echo
  186. note " Fix a denied grant:"
  187. note " 1) System Settings → Privacy & Security → <Service> → toggle app off then on"
  188. note " 2) Or: tccutil reset <ServiceShortName> <bundle-id>"
  189. note " e.g. tccutil reset ScreenCapture com.tinyspeck.slackmacgap"
  190. note " See references/tcc-mechanics.md for the full service catalog."
  191. fi