Files
fn_registry/bash/functions/browser/clean_chrome_profile_extensions.sh
T
Egutierrez e0fad0e82f feat(browser): clean_chrome_profile_extensions + fix policy backup en managed/
Rediseño de apply_chromium_extension_policy y nueva función de purga in-place,
tras resolver por qué las extensiones bloqueadas reaparecían en Chromium 148.

- apply_chromium_extension_policy: añade --block (ExtensionInstallBlocklist).
  Reemplaza el modo ExtensionSettings "*": blocked (que rompía las extensiones
  unpacked vía --load-extension, p.ej. la de captura de web_proxy con el error
  'Loading of unpacked extensions is disabled by the administrator') por una
  blocklist específica. FIX RAÍZ: los backups se guardan fuera de policies/managed/
  (en policy-backups/), porque Chromium lee TODOS los archivos del directorio
  managed/ sin filtrar extensión de nombre — un extensions.json.bak ahí se mergea
  con la policy y reinyecta las extensiones del backup (location=7).
- clean_chrome_profile_extensions (nueva): purga in-place de un perfil existente
  (borra carpetas de Extensions/ + refs en Preferences/Secure Preferences) dejando
  solo la whitelist. Complementa la policy: la policy evita reinstalación, esta
  desinstala lo ya presente. Requiere chromium cerrado.

Ambas: dominio browser, grupo navegator, guard de auto-ejecución, dry-run.
2026-06-05 17:13:49 +02:00

246 lines
10 KiB
Bash

#!/usr/bin/env bash
# clean_chrome_profile_extensions — purga in-place extensiones fuera de la whitelist
# de un perfil Chrome/Chromium existente. Borra las carpetas de disco y limpia las
# referencias en Preferences y Secure Preferences para que Chromium no las reinstale.
set -euo pipefail
clean_chrome_profile_extensions() {
# ── defaults ──────────────────────────────────────────────────────────────
local _user_data_dir="${HOME}/.config/chromium"
local _profile_dir="Default"
local _keep=()
local _default_ext="ddkjiahejlhfcafbddmgiahcphecmpfh"
local _dry_run=0
# ── parse args ─────────────────────────────────────────────────────────────
_usage() {
cat >&2 <<'EOF'
Usage: clean_chrome_profile_extensions [--user-data-dir <dir>] [--profile-directory <name>]
[--keep <ext_id>]... [--dry-run]
--user-data-dir Raíz del perfil. Default: ~/.config/chromium
--profile-directory Subperfil. Default: Default
--keep <ext_id> ID de extensión a conservar (repetible).
Default si no se pasa ninguno: ddkjiahejlhfcafbddmgiahcphecmpfh (uBlock Origin Lite)
--dry-run Lista qué se borraría sin tocar nada.
Exit codes:
0 éxito (o dry-run completado)
1 error de argumento o validación
2 chromium está corriendo (solo en modo real)
3 directorio de extensiones no encontrado
EOF
return 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--user-data-dir) _user_data_dir="$2"; shift 2 ;;
--profile-directory) _profile_dir="$2"; shift 2 ;;
--keep) _keep+=("$2"); shift 2 ;;
--dry-run) _dry_run=1; shift ;;
-h|--help) _usage; return 0 ;;
*) echo "clean_chrome_profile_extensions: argumento desconocido: $1" >&2; return 1 ;;
esac
done
# ── whitelist por defecto ──────────────────────────────────────────────────
if [[ ${#_keep[@]} -eq 0 ]]; then
_keep=("$_default_ext")
fi
# ── construir paths base ───────────────────────────────────────────────────
local _profile_path
_profile_path="${_user_data_dir}/${_profile_dir}"
local _ext_dir="${_profile_path}/Extensions"
# ── validaciones ──────────────────────────────────────────────────────────
if [[ ! -d "$_profile_path" ]]; then
echo "clean_chrome_profile_extensions: perfil no encontrado: ${_profile_path}" >&2
return 1
fi
if [[ ! -d "$_ext_dir" ]]; then
echo "clean_chrome_profile_extensions: directorio de extensiones no encontrado: ${_ext_dir}" >&2
return 3
fi
# ── guard: chromium NO debe estar corriendo (excepto en dry-run) ──────────
if [[ $_dry_run -eq 0 ]]; then
if pgrep -x chromium >/dev/null 2>&1; then
echo "clean_chrome_profile_extensions: chromium está corriendo — ciérralo antes de limpiar:" >&2
echo " pkill -TERM chromium" >&2
echo "(Chromium reescribe Preferences desde memoria al cerrar y desharía la purga)" >&2
return 2
fi
fi
# ── enumerar extensiones instaladas ───────────────────────────────────────
local _to_remove=()
local _to_keep=()
while IFS= read -r -d '' _ext_path; do
local _ext_id
_ext_id="$(basename "$_ext_path")"
# Siempre ignorar la carpeta Temp (usada durante installs en curso)
if [[ "$_ext_id" == "Temp" ]]; then
continue
fi
# Comprobar si está en la whitelist
local _in_keep=0
local _k
for _k in "${_keep[@]}"; do
if [[ "$_ext_id" == "$_k" ]]; then
_in_keep=1
break
fi
done
if [[ $_in_keep -eq 1 ]]; then
_to_keep+=("$_ext_id")
else
_to_remove+=("$_ext_id")
fi
done < <(find "$_ext_dir" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
# ── modo dry-run: solo informar ────────────────────────────────────────────
if [[ $_dry_run -eq 1 ]]; then
echo "=== clean_chrome_profile_extensions DRY-RUN ===" >&2
echo " Perfil : ${_profile_path}" >&2
echo " Conservar (${#_to_keep[@]}): ${_to_keep[*]+"${_to_keep[*]}"}" >&2
echo " Borrar (${#_to_remove[@]}): ${_to_remove[*]+"${_to_remove[*]}"}" >&2
_emit_json "$_profile_path" _to_keep _to_remove
return 0
fi
# ── borrar extensiones fuera de la whitelist ───────────────────────────────
if [[ ${#_to_remove[@]} -gt 0 ]]; then
local _id
for _id in "${_to_remove[@]}"; do
rm -rf "${_ext_dir}/${_id}"
done
# ── purgar referencias en Preferences y Secure Preferences ────────────
# Construir lista Python de IDs eliminados
local _py_ids_list=""
for _id in "${_to_remove[@]}"; do
_py_ids_list+="\"${_id}\","
done
_py_ids_list="[${_py_ids_list%,}]"
local _today
_today="$(date +%Y%m%d)"
local _prefs_file
for _prefs_file in "${_profile_path}/Preferences" "${_profile_path}/Secure Preferences"; do
if [[ ! -f "$_prefs_file" ]]; then
continue
fi
# Backup (no sobreescribir backup del mismo día)
local _backup="${_prefs_file}.bak.${_today}"
if [[ ! -f "$_backup" ]]; then
cp "$_prefs_file" "$_backup"
fi
# Editar con python3 si está disponible
if command -v python3 >/dev/null 2>&1; then
python3 - "$_prefs_file" "$_py_ids_list" <<'PY' || \
echo "clean_chrome_profile_extensions: advertencia — no se pudo purgar ${_prefs_file} con python3" >&2
import sys, json
prefs_path = sys.argv[1]
removed_ids = json.loads(sys.argv[2])
with open(prefs_path, "r", encoding="utf-8") as f:
data = json.load(f)
# 1. extensions.settings.<id>
ext_settings = data.get("extensions", {}).get("settings", {})
for ext_id in removed_ids:
ext_settings.pop(ext_id, None)
# 2. extensions.pinned_extensions (lista de IDs)
pinned = data.get("extensions", {}).get("pinned_extensions", None)
if isinstance(pinned, list):
data["extensions"]["pinned_extensions"] = [
pid for pid in pinned if pid not in removed_ids
]
# 3. protection.macs.extensions.settings.<id> (Secure Preferences HMAC table)
try:
mac_ext = data["protection"]["macs"]["extensions"]["settings"]
for ext_id in removed_ids:
mac_ext.pop(ext_id, None)
except (KeyError, TypeError):
pass
with open(prefs_path, "w", encoding="utf-8") as f:
json.dump(data, f, separators=(",", ":"))
PY
# Fallback con jq si python3 no está disponible
elif command -v jq >/dev/null 2>&1; then
local _tmp_prefs
_tmp_prefs="$(mktemp)"
local _jq_del=""
for _id in "${_to_remove[@]}"; do
_jq_del+=" | del(.extensions.settings[\"${_id}\"])"
_jq_del+=" | del(.protection.macs.extensions.settings[\"${_id}\"])"
done
# pinned_extensions como lista
_jq_del+=" | if .extensions.pinned_extensions then .extensions.pinned_extensions -= [$(printf '"%s",' "${_to_remove[@]}" | sed 's/,$//')] else . end"
jq "${_jq_del:1}" "$_prefs_file" > "$_tmp_prefs" && mv "$_tmp_prefs" "$_prefs_file" || {
echo "clean_chrome_profile_extensions: advertencia — jq falló procesando ${_prefs_file}" >&2
rm -f "$_tmp_prefs"
}
else
echo "clean_chrome_profile_extensions: advertencia — ni python3 ni jq disponibles; se borraron las carpetas pero no las referencias en $(basename "$_prefs_file")" >&2
fi
done
fi
# ── emitir resultado JSON ──────────────────────────────────────────────────
_emit_json "$_profile_path" _to_keep _to_remove
}
# ── helpers ────────────────────────────────────────────────────────────────────
# _json_array_from_nameref <nameref>
# Convierte un array bash (pasado por nombre de variable) en JSON array de strings.
_json_array_from_nameref() {
local -n _arr_ref="$1"
local _out="["
local _first=1
local _item
for _item in "${_arr_ref[@]+"${_arr_ref[@]}"}"; do
if [[ $_first -eq 1 ]]; then
_out+="\"${_item}\""
_first=0
else
_out+=",\"${_item}\""
fi
done
_out+="]"
echo "$_out"
}
# _emit_json <profile_path> <kept_nameref> <removed_nameref>
_emit_json() {
local _p="$1"
local _kept_json
_kept_json="$(_json_array_from_nameref "$2")"
local _removed_json
_removed_json="$(_json_array_from_nameref "$3")"
printf '{"profile":"%s","kept":%s,"removed":%s}\n' \
"$_p" "$_kept_json" "$_removed_json"
}
# ── auto-ejecución ────────────────────────────────────────────────────────────
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
clean_chrome_profile_extensions "$@"
fi