#!/bin/bash # # After Effects Plugin Diagnostic Tool # Checks dependencies, code signing, quarantine, and plugin structure # # Usage: bash check_plugin.sh /path/to/Plugin.plugin # # Don't exit on error - we want to continue checking even if some commands fail set +e # Output file on Desktop REPORT_TIMESTAMP=$(date +%Y%m%d_%H%M%S) OUTPUT_FILE="$HOME/Desktop/plugin_diagnostic_$REPORT_TIMESTAMP.txt" # Function to output to both console and file log() { echo -e "$1" # Strip color codes for file output echo -e "$1" | sed 's/\x1b\[[0-9;]*m//g' >> "$OUTPUT_FILE" } # Initialize output file echo "# After Effects Plugin Diagnostic Report" > "$OUTPUT_FILE" echo "# Generated: $(date)" >> "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE" # Colors (for console only) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Icons PASS="✅" FAIL="❌" WARN="⚠️" INFO="ℹ️" print_header() { log "" log "${BLUE}═══════════════════════════════════════════════════════════${NC}" log "${BLUE} $1${NC}" log "${BLUE}═══════════════════════════════════════════════════════════${NC}" } print_section() { log "" log "${YELLOW}── $1 ──${NC}" } # Default plugin path for AIColorMatch DEFAULT_PLUGIN="/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore/BSKL/AIColorMatch.plugin" MEDIACORE_PATH="/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore" # Check arguments - use default if not provided if [ -z "$1" ]; then if [ -d "$DEFAULT_PLUGIN" ]; then PLUGIN_PATH="$DEFAULT_PLUGIN" echo "Using default plugin path: $PLUGIN_PATH" else echo -e "${RED}Usage: bash check_plugin.sh /path/to/Plugin.plugin${NC}" echo "" echo "Default plugin not found at:" echo " $DEFAULT_PLUGIN" echo "" echo "Please specify plugin path manually." exit 1 fi else PLUGIN_PATH="$1" fi # Validate plugin exists if [ ! -d "$PLUGIN_PATH" ]; then log "${FAIL} Plugin not found: $PLUGIN_PATH" exit 1 fi # Get plugin name PLUGIN_NAME=$(basename "$PLUGIN_PATH" .plugin) print_header "After Effects Plugin Diagnostic Report" log "" log "Plugin: ${GREEN}$PLUGIN_NAME${NC}" log "Path: $PLUGIN_PATH" log "Date: $(date)" log "macOS: $(sw_vers -productVersion) ($(sw_vers -buildVersion))" log "Machine: $(uname -m)" # ───────────────────────────────────────────────────────────── # 0. ALL PLUGINS IN MEDIACORE # ───────────────────────────────────────────────────────────── print_section "All Plugins in MediaCore" if [ -d "$MEDIACORE_PATH" ]; then log "${BLUE}Scanning: $MEDIACORE_PATH${NC}" log "" # Find all .plugin bundles PLUGIN_COUNT=0 while IFS= read -r plugin_bundle; do PLUGIN_COUNT=$((PLUGIN_COUNT + 1)) plugin_name=$(basename "$plugin_bundle" .plugin) plugin_modified=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$plugin_bundle" 2>/dev/null || echo "unknown") plugin_size=$(du -sh "$plugin_bundle" 2>/dev/null | cut -f1 || echo "?") plugin_perms=$(stat -f "%Sp" "$plugin_bundle" 2>/dev/null || echo "?") plugin_owner=$(stat -f "%Su:%Sg" "$plugin_bundle" 2>/dev/null || echo "?") # Check if binary exists binary_path="$plugin_bundle/Contents/MacOS/$plugin_name" if [ -f "$binary_path" ]; then arch=$(lipo -archs "$binary_path" 2>/dev/null || echo "?") binary_perms=$(stat -f "%Sp" "$binary_path" 2>/dev/null || echo "?") # Check code signing codesign_check=$(codesign -dv --verbose=2 "$plugin_bundle" 2>&1) if echo "$codesign_check" | grep -q "Authority="; then signer=$(echo "$codesign_check" | grep "Authority=" | head -1 | cut -d= -f2 | cut -c1-40) sign_status="$PASS" elif echo "$codesign_check" | grep -q "Signature=adhoc"; then signer="ad-hoc" sign_status="$WARN" elif echo "$codesign_check" | grep -q "TeamIdentifier="; then # Signed but no Authority line (Developer ID) team=$(echo "$codesign_check" | grep "TeamIdentifier=" | cut -d= -f2) signer="Team: $team" sign_status="$PASS" else signer="unsigned" sign_status="$FAIL" fi # Check notarization if echo "$codesign_check" | grep -q "Notarization Ticket"; then notarized="yes" else notarized="no" fi # Check quarantine quarantine_check=$(xattr -p com.apple.quarantine "$plugin_bundle" 2>/dev/null) if [ -n "$quarantine_check" ]; then qflag="$WARN quarantined" else qflag="" fi log "$sign_status $plugin_name $qflag" log " Modified: $plugin_modified | Size: $plugin_size | Arch: $arch" log " Perms: $plugin_perms | Binary: $binary_perms | Owner: $plugin_owner" log " Signed: $signer | Notarized: $notarized" else log "$WARN $plugin_name (binary missing)" log " Modified: $plugin_modified | Size: $plugin_size" log " Perms: $plugin_perms | Owner: $plugin_owner" fi log "" done < <(find "$MEDIACORE_PATH" -maxdepth 2 -name "*.plugin" -type d 2>/dev/null | sort) log "Total plugins found: $PLUGIN_COUNT" else log "$WARN MediaCore directory not found: $MEDIACORE_PATH" fi # ───────────────────────────────────────────────────────────── # 1. PLUGIN STRUCTURE # ───────────────────────────────────────────────────────────── print_section "Plugin Structure" BINARY_PATH="$PLUGIN_PATH/Contents/MacOS/$PLUGIN_NAME" PLIST_PATH="$PLUGIN_PATH/Contents/Info.plist" RSRC_PATH="$PLUGIN_PATH/Contents/Resources/$PLUGIN_NAME.rsrc" if [ -f "$BINARY_PATH" ]; then log "$PASS Binary: $BINARY_PATH" BINARY_SIZE=$(du -h "$BINARY_PATH" | cut -f1) log " Size: $BINARY_SIZE" else log "$FAIL Binary NOT FOUND: $BINARY_PATH" fi if [ -f "$PLIST_PATH" ]; then log "$PASS Info.plist: exists" else log "$FAIL Info.plist NOT FOUND" fi if [ -f "$RSRC_PATH" ]; then log "$PASS Resource file: $PLUGIN_NAME.rsrc" else log "$WARN Resource file not found (may be embedded)" fi # ───────────────────────────────────────────────────────────── # 2. ARCHITECTURE # ───────────────────────────────────────────────────────────── print_section "Architecture" if [ -f "$BINARY_PATH" ]; then ARCHS=$(lipo -archs "$BINARY_PATH" 2>/dev/null || echo "unknown") if [[ "$ARCHS" == *"arm64"* ]] && [[ "$ARCHS" == *"x86_64"* ]]; then log "$PASS Universal Binary: $ARCHS" elif [[ "$ARCHS" == *"arm64"* ]]; then log "$WARN ARM64 only (no Intel support)" elif [[ "$ARCHS" == *"x86_64"* ]]; then log "$WARN Intel only (no Apple Silicon native)" else log "$FAIL Unknown architecture: $ARCHS" fi # Current machine MACHINE_ARCH=$(uname -m) log "$INFO Current machine: $MACHINE_ARCH" fi # ───────────────────────────────────────────────────────────── # 3. DEPENDENCIES # ───────────────────────────────────────────────────────────── print_section "Dependencies" if [ -f "$BINARY_PATH" ]; then MISSING_DEPS=0 # Get unique dependencies (otool shows both archs for universal binary) while IFS= read -r line; do # Skip empty lines and the binary path itself [[ -z "$line" ]] && continue [[ "$line" == *"$BINARY_PATH"* ]] && continue # Extract library path LIB_PATH=$(echo "$line" | sed 's/^[[:space:]]*//' | cut -d' ' -f1) # Skip if empty [[ -z "$LIB_PATH" ]] && continue # Categorize # Note: Since macOS Big Sur, /usr/lib libraries are in shared cache # and don't exist as physical files, but they still work if [[ "$LIB_PATH" == /usr/lib/* ]]; then log "$PASS System: $(basename "$LIB_PATH") (shared cache)" elif [[ "$LIB_PATH" == /System/Library/* ]]; then if [ -d "${LIB_PATH%/*}" ] || [ -f "$LIB_PATH" ]; then log "$PASS Framework: $(echo "$LIB_PATH" | grep -o '[^/]*\.framework')" else log "$FAIL MISSING: $LIB_PATH" MISSING_DEPS=$((MISSING_DEPS + 1)) fi elif [[ "$LIB_PATH" == @rpath/* ]] || [[ "$LIB_PATH" == @loader_path/* ]] || [[ "$LIB_PATH" == @executable_path/* ]]; then log "$WARN Relative: $LIB_PATH" elif [ -f "$LIB_PATH" ]; then log "$PASS External: $LIB_PATH" else log "$FAIL MISSING: $LIB_PATH" MISSING_DEPS=$((MISSING_DEPS + 1)) fi done < <(otool -L "$BINARY_PATH" 2>/dev/null | sort -u) if [ $MISSING_DEPS -gt 0 ]; then log "" log "${RED}$FAIL Found $MISSING_DEPS missing dependencies!${NC}" else log "" log "${GREEN}$PASS All dependencies found${NC}" fi fi # ───────────────────────────────────────────────────────────── # 4. CODE SIGNING & NOTARIZATION # ───────────────────────────────────────────────────────────── print_section "Code Signing & Notarization" CODESIGN_OUTPUT=$(codesign -dv --verbose=4 "$PLUGIN_PATH" 2>&1) SIGNING_ISSUE=0 # Check signing status if echo "$CODESIGN_OUTPUT" | grep -q "Authority="; then # Extract all authorities (certificate chain) log "${BLUE}Certificate Chain:${NC}" echo "$CODESIGN_OUTPUT" | grep "Authority=" | while read -r line; do CERT=$(echo "$line" | cut -d= -f2) log " $CERT" done # Team ID TEAM_ID=$(echo "$CODESIGN_OUTPUT" | grep "TeamIdentifier=" | cut -d= -f2) if [ -n "$TEAM_ID" ] && [ "$TEAM_ID" != "not set" ]; then log "$PASS Team ID: $TEAM_ID" else log "$WARN Team ID not set" fi # Timestamp SIGN_TIMESTAMP=$(echo "$CODESIGN_OUTPUT" | grep "Timestamp=" | cut -d= -f2) if [ -n "$SIGN_TIMESTAMP" ]; then log "$PASS Signed on: $SIGN_TIMESTAMP" else log "$WARN No timestamp (signature may expire with certificate)" fi # Runtime version (hardened runtime) RUNTIME=$(echo "$CODESIGN_OUTPUT" | grep "Runtime Version=" | cut -d= -f2) if [ -n "$RUNTIME" ]; then log "$PASS Hardened Runtime: $RUNTIME" else log "$WARN Hardened runtime not enabled" fi # Notarization check if echo "$CODESIGN_OUTPUT" | grep -q "Notarization Ticket"; then log "$PASS Notarization ticket stapled" else log "$WARN Notarization ticket NOT stapled" SIGNING_ISSUE=1 fi elif echo "$CODESIGN_OUTPUT" | grep -q "adhoc"; then log "$WARN Ad-hoc signed (development only)" SIGNING_ISSUE=1 else log "$FAIL NOT SIGNED" SIGNING_ISSUE=1 fi # Verify signature integrity log "" log "${BLUE}Signature Verification:${NC}" VERIFY_OUTPUT=$(codesign --verify --deep --strict "$PLUGIN_PATH" 2>&1) if [ $? -eq 0 ]; then log "$PASS Signature integrity: valid" else log "$FAIL Signature integrity: INVALID" log " $VERIFY_OUTPUT" SIGNING_ISSUE=1 fi # Gatekeeper assessment (spctl) log "" log "${BLUE}Gatekeeper Assessment:${NC}" SPCTL_OUTPUT=$(spctl --assess --type execute -v "$PLUGIN_PATH" 2>&1) SPCTL_EXIT=$? if [ $SPCTL_EXIT -eq 0 ]; then log "$PASS Gatekeeper: accepted" elif echo "$SPCTL_OUTPUT" | grep -q "rejected"; then # Check the reason if echo "$SPCTL_OUTPUT" | grep -q "notarized"; then log "$WARN Gatekeeper: requires notarization" elif echo "$SPCTL_OUTPUT" | grep -q "not.*app"; then log "$PASS Gatekeeper: valid code (not an app bundle)" else log "$FAIL Gatekeeper: rejected" log " $SPCTL_OUTPUT" fi else log "$INFO Gatekeeper: $SPCTL_OUTPUT" fi # Check if notarization can be verified online log "" log "${BLUE}Notarization Status (online check):${NC}" STAPLER_OUTPUT=$(xcrun stapler validate "$PLUGIN_PATH" 2>&1) if echo "$STAPLER_OUTPUT" | grep -q "valid"; then log "$PASS Notarization: verified" elif echo "$STAPLER_OUTPUT" | grep -q "not valid"; then log "$FAIL Notarization: NOT valid or not notarized" SIGNING_ISSUE=1 else log "$INFO $STAPLER_OUTPUT" fi # ───────────────────────────────────────────────────────────── # 5. QUARANTINE # ───────────────────────────────────────────────────────────── print_section "Quarantine Status" QUARANTINE=$(xattr -p com.apple.quarantine "$PLUGIN_PATH" 2>/dev/null || echo "") if [ -n "$QUARANTINE" ]; then log "$WARN Quarantine flag is SET" log " Value: $QUARANTINE" log "" log "${YELLOW}To remove quarantine, run:${NC}" log " xattr -cr \"$PLUGIN_PATH\"" else log "$PASS No quarantine flag" fi # ───────────────────────────────────────────────────────────── # 6. INFO.PLIST CHECK # ───────────────────────────────────────────────────────────── print_section "Info.plist Analysis" if [ -f "$PLIST_PATH" ]; then # CFBundleIdentifier BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$PLIST_PATH" 2>/dev/null || echo "") if [ -n "$BUNDLE_ID" ] && [ "$BUNDLE_ID" != "" ]; then log "$PASS CFBundleIdentifier: $BUNDLE_ID" else log "$FAIL CFBundleIdentifier is EMPTY!" log " ${YELLOW}This may cause loading issues on some systems${NC}" fi # CFBundleExecutable BUNDLE_EXEC=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$PLIST_PATH" 2>/dev/null || echo "") if [ "$BUNDLE_EXEC" = "$PLUGIN_NAME" ]; then log "$PASS CFBundleExecutable: $BUNDLE_EXEC" else log "$WARN CFBundleExecutable: $BUNDLE_EXEC (expected: $PLUGIN_NAME)" fi # LSMinimumSystemVersion MIN_VERSION=$(/usr/libexec/PlistBuddy -c "Print :LSMinimumSystemVersion" "$PLIST_PATH" 2>/dev/null || echo "not set") log "$INFO Minimum macOS: $MIN_VERSION" # Current macOS version CURRENT_MACOS=$(sw_vers -productVersion) log "$INFO Current macOS: $CURRENT_MACOS" fi # ───────────────────────────────────────────────────────────── # 7. ENTRY POINT CHECK (via nm) # ───────────────────────────────────────────────────────────── print_section "Entry Point" if [ -f "$BINARY_PATH" ]; then if nm "$BINARY_PATH" 2>/dev/null | grep -q "_EffectMain"; then log "$PASS EffectMain symbol exported" elif nm "$BINARY_PATH" 2>/dev/null | grep -q "_main"; then log "$WARN Found _main but not _EffectMain" else log "$FAIL No EffectMain symbol found!" fi fi # ───────────────────────────────────────────────────────────── # 8. FILE PERMISSIONS & OWNERSHIP # ───────────────────────────────────────────────────────────── print_section "File Permissions & Ownership" PERM_ISSUES=0 CURRENT_USER=$(whoami) log "${BLUE}All files in plugin bundle:${NC}" log "" # Check each file and directory while IFS= read -r item; do # Get permissions, owner, group STAT_OUTPUT=$(stat -f "%Sp %Su %Sg" "$item" 2>/dev/null) PERMS=$(echo "$STAT_OUTPUT" | cut -d' ' -f1) OWNER=$(echo "$STAT_OUTPUT" | cut -d' ' -f2) GROUP=$(echo "$STAT_OUTPUT" | cut -d' ' -f3) # Get relative path for display REL_PATH="${item#$PLUGIN_PATH}" [ -z "$REL_PATH" ] && REL_PATH="/" # Determine status icon HAS_ISSUE=0 # Check if it's a directory or file if [ -d "$item" ]; then ITEM_TYPE="d" # Directories should be readable and executable (r-x) by all if [[ "$PERMS" != d*r?x*r?x*r?x* ]] && [[ "$PERMS" != d*r?x*r?x* ]]; then HAS_ISSUE=1 fi else # Files should be readable if [[ "$PERMS" != -*r* ]]; then HAS_ISSUE=1 fi # Binary should be executable if [ "$item" = "$BINARY_PATH" ] && [[ "$PERMS" != -*x* ]]; then HAS_ISSUE=1 fi fi # Always show the file with its permissions if [ $HAS_ISSUE -eq 1 ]; then log "$FAIL $PERMS $OWNER:$GROUP $REL_PATH" PERM_ISSUES=$((PERM_ISSUES + 1)) else log "$PASS $PERMS $OWNER:$GROUP $REL_PATH" fi done < <(find "$PLUGIN_PATH" -print 2>/dev/null) # Summary of permissions log "" if [ $PERM_ISSUES -eq 0 ]; then log "${GREEN}$PASS All permissions look correct${NC}" else log "${RED}$FAIL Found $PERM_ISSUES permission issue(s)${NC}" log "" log "${YELLOW}To fix permissions, run:${NC}" log " sudo chmod -R a+rX \"$PLUGIN_PATH\"" log " sudo chmod a+x \"$BINARY_PATH\"" fi # Show ownership summary log "" log "${BLUE}Ownership Summary:${NC}" # Get unique owners OWNERS=$(find "$PLUGIN_PATH" -print0 2>/dev/null | xargs -0 stat -f "%Su" | sort -u) for owner in $OWNERS; do COUNT=$(find "$PLUGIN_PATH" -user "$owner" 2>/dev/null | wc -l | tr -d ' ') log " $owner: $COUNT items" done # ───────────────────────────────────────────────────────────── # 9. EXTENDED ATTRIBUTES # ───────────────────────────────────────────────────────────── print_section "Extended Attributes" # Check for any extended attributes on all files XATTR_FILES=0 log "${BLUE}Checking for extended attributes on all files...${NC}" log "" while IFS= read -r item; do XATTRS=$(xattr "$item" 2>/dev/null) if [ -n "$XATTRS" ]; then REL_PATH="${item#$PLUGIN_PATH}" [ -z "$REL_PATH" ] && REL_PATH="/" log "$WARN $REL_PATH" echo "$XATTRS" | while read -r attr; do if [ -n "$attr" ]; then log " $attr" fi done XATTR_FILES=$((XATTR_FILES + 1)) fi done < <(find "$PLUGIN_PATH" -print 2>/dev/null) if [ $XATTR_FILES -eq 0 ]; then log "${GREEN}$PASS No extended attributes found${NC}" else log "" log "${YELLOW}Found extended attributes on $XATTR_FILES file(s)${NC}" log "${YELLOW}To remove all extended attributes, run:${NC}" log " xattr -cr \"$PLUGIN_PATH\"" fi # ───────────────────────────────────────────────────────────── # SUMMARY # ───────────────────────────────────────────────────────────── print_header "Summary" ISSUES=0 # Check for common issues if [ -z "$BUNDLE_ID" ] || [ "$BUNDLE_ID" = "" ]; then log "$FAIL Empty CFBundleIdentifier - may cause 'entry point' errors" ISSUES=$((ISSUES + 1)) fi if [ -n "$QUARANTINE" ]; then log "$WARN Quarantine flag set - may block plugin loading" ISSUES=$((ISSUES + 1)) fi if [ $MISSING_DEPS -gt 0 ]; then log "$FAIL Missing dependencies detected" ISSUES=$((ISSUES + 1)) fi if [ $SIGNING_ISSUE -gt 0 ]; then log "$WARN Code signing or notarization issues detected" ISSUES=$((ISSUES + 1)) fi if [ $PERM_ISSUES -gt 0 ]; then log "$FAIL Permission issues detected" ISSUES=$((ISSUES + 1)) fi if [ $ISSUES -eq 0 ]; then log "${GREEN}$PASS No issues detected${NC}" else log "" log "${YELLOW}Found $ISSUES potential issue(s)${NC}" log "" log "${BLUE}Quick fixes:${NC}" if [ -n "$QUARANTINE" ]; then log " Remove quarantine: xattr -cr \"$PLUGIN_PATH\"" fi if [ $PERM_ISSUES -gt 0 ]; then log " Fix permissions: sudo chmod -R a+rX \"$PLUGIN_PATH\"" fi fi # ───────────────────────────────────────────────────────────── # OUTPUT FILE INFO # ───────────────────────────────────────────────────────────── log "" log "═══════════════════════════════════════════════════════════" log "" log "${GREEN}📄 Report saved to:${NC}" log " $OUTPUT_FILE" log "" log "${YELLOW}Please send this file to support for analysis.${NC}" log "" # Also print to console without colors for clarity echo "" echo "════════════════════════════════════════════════════════════" echo "" echo "📄 Report saved to Desktop:" echo " $OUTPUT_FILE" echo "" echo "👉 Please send this file to BSKL support for analysis." echo ""