This commit is contained in:
Tomas Mirchev 2025-10-19 00:50:31 +02:00
commit cdccf85e4d
10 changed files with 673 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
assets/

200
README.md Normal file
View File

@ -0,0 +1,200 @@
# Brave Settings Backup
## Quick: Manual intervention
When opening the browser using the backup, these are the only need actions:
- brave://settings/getStarted
- Get started > On startup > Open the New Tab Page
- brave://settings/search
- Set Search engine
## Explanation
Goal: Backup Brave Settings while disabling unnecessary features.
Resources at:
- Brave: https://support.brave.app/hc/en-us/articles/360039248271-Group-Policy
- Google: https://chromeenterprise.google/intl/en_us/policies/
### Using Group-Based Policies (GBP)
There are two groups:
- Mandatory (system-wide) <- use this one
- Stored at `/Library/Managed Preferences/com.brave.Browser.plist`
- It is an editable file. This tool can be used too `PlistBuddy`
- Commands:
```
sudo mkdir -p "/Library/Managed Preferences"
sudo chown root:wheel "/Library/Managed Preferences"
sudo chmod 755 "/Library/Managed Preferences"
# Run this see the effect
sudo killall cfprefsd
```
- Recommended (user-level)
- Stored at: `~/Library/Preferences/com.brave.Browser.plist`
- It is a binary and can be used via:
- `defaults read com.brave.Browser`
- `defaults delete com.brave.Browser`
- `defaults write com.brave.Browser DefaultSearchProviderEnabled -bool true`
- `defaults import com.brave.Browser ./com.brave.Browser.plist`
Some policies only work if the instance is managed via MDM.
Because of that, we have to rely on preferences too.
### Using Preferences
This method is not recommened neither is documented; however, there is no other alternative.
Whenever the browser is opened for the first time, this dir is created and populated:
- `~/Library/Application Support/BraveSoftware/Brave-Browser`
Note: For testing purposes, after everything is backed up, feel free to completely delete it.
Inside, there are four main files:
- `Brave-Browser/First Run` (empty file used as flag to skip "Welcome page")
- `Brave-Browser/Local State`
- `Brave-Browser/<Default_or_Profile>/Preferences`
- `Brave-Browser/<Default_or_Profile>/Secure Preferences`
How the browser starts up (as a new tab, for instance) and the default search engine are
preferences stored in "Secure Preferences", and these values are hashed to ensure the integrity.
This means that the file cannot be edited manually: only via UI or by backing up the whole file.
These hashes change based on the machine, so be aware.
## How does the backup work?
There are a few files:
```
.
├── _assets All settings.
│   ├── Default
│   │   └── Preferences
│   ├── First Run
│   └── Local State
├── assets Settings with hashes. Not important and can be deleted.
│   ├── Default
│   │   ├── Preferences
│   │   └── Secure Preferences
│   ├── First Run
│   └── Local State
├── policies Browser policies. Generated by `brave_set_policies.py`
│   └── com.brave.Browser.plist
├── README.md
├── brave_debug.sh
├── brave_set_assets.py If `assets` dir not present, uses `_assets`.
├── brave_set_policies.py
└── brave_update_assets.sh
```
First, make sure to delete these files:
```
rm -rf ./assets
rm -rf '~/Library/Application Support/BraveSoftware/Brave-Browser'
```
Set `policies/com.brave.Browser.plist` by executing `./brave_set_policies.py`.
You can check them at: `brave://policy`.
Set `_assets` by executing `./brave_set_assets.py`.
Open the browser and manually follow the steps in "Manual intervention". Close the browser.
Run `./brave_update_assets.sh` to copy over `assets` the preferences with the new proper hashes.
## Debug or find new preferences
The script `./brave_debug.sh` helps finding the preference property by comparing the files.
After deleting the directory `~/Library/Application Support/BraveSoftware/Brave-Browser`,
open the browser (to load the `Brave-Browser` dir), close it and run `./brave_debug.sh backup init`.
Open agian the browser, change the desired settings, close it and run `./brave_debug.sh diff init`.
This will compare the old files with the new ones and help you find the differences.
The script basically creates named checkpoints in `/tmp`
following the structure `./brave_debug.sh <command> <checkpoint_name>`.
## All preferences files
- "Brave-Browser/Local State"
- Create empty file "Brave-Browser/First Run"
- Check asset file
- "Brave-Browser/Default/Preferences"
- Check asset file
- "Brave-Browser/Default/Secure Preferences"
- Search engine is set in "Default/Preferences" but unless
it is hashed in "Default/Secure Preferences", browser does not set it.
- `restore_on_startup: 5` sets "On startup: Open the New Tab Page". It needs to be hashed too.
```
{
"session": {
"restore_on_startup": 5
}
}
```
## All settings mapped
---
Brave 1.83.118 (Official Build) (arm64)
Chromium: 141.0.7390.108
---
### Get started
- On startup >> "Open the New Tag page"
- [Secure Preferences]:session.restore_on_startup
- New tab page shows >> "Blank page"
- brave.new_tab_page.shows_options
### Appearance
- Show tab groups in bookmarks >> OFF
- bookmark_bar.show_tab_groups
- Automatically pin new tab groups created on any device to the bookmarks >> OFF
- auto_pin_new_tab_groups
### Shields
- Show the number of blocked items on the Shields icon >> OFF
- brave.shields.stats_badge_visible
- Store contact information for future broken site reports >> OFF
- brave.webcompat.report.contact_info: "" (empty string)
- brave.webcompat.report.enable_save_contact_info: false
### Privacy and security
- Only resolve .onion addresses in Tor windows >> OFF
- tor.onion_only_in_tor_windows
- Allow privacy-preserving product analytics (P3A) >> OFF
- [Local State]:brave.p3a.enabled
- Automatically send diagnostic reports >> OFF
- [Local State]:brave.user_experience_metrics.reporting_enabled
### Search engine
- Improve search suggestions >> OFF
- search.suggest_enabled
### Extensions
- Media Router >> OFF
- media_router.enable_media_router
- Widevine >> ON
- brave.widevine_opted_in
### Autofill and passwords
- (Passwords.Settings) Sign in automatically >> OFF
- credentials_enable_autosignin
- Allow auto-fill in private windows >> OFF
- brave.autofill_private_windows
### Languages
- Websites in your languages >> "en"
- intl.accept_languages
- intl.selected_languages
### System
- Memory Saver >> ON
- [Local State]:performance_tuning.high_efficiency_mode
- Energy Saver >> OFF
- [Local State]:performance_tuning.battery_saver_mode

View File

@ -0,0 +1,37 @@
{
"brave": {
"new_tab_page": {
"shows_options": 2
},
"webcompat": {
"report": {
"contact_info": "",
"enable_save_contact_info": false
}
},
"shields": {
"stats_badge_visible": false
},
"autofill_private_windows": false,
"other_search_engines_enabled": true,
"show_side_panel_button": false
},
"intl": {
"accept_languages": "en",
"selected_languages": "en"
},
"media_router": {
"enable_media_router": false
},
"tor": {
"onion_only_in_tor_windows": false
},
"search": {
"suggest_enabled": false
},
"credentials_enable_autosignin": false,
"bookmark_bar": {
"show_tab_groups": false
},
"auto_pin_new_tab_groups": false
}

0
_assets/First Run Normal file
View File

25
_assets/Local State Normal file
View File

@ -0,0 +1,25 @@
{
"brave": {
"first_run_finished": true,
"p3a": {
"enabled": false
},
"user_experience_metrics": {
"reporting_enabled": false
},
"widevine_opted_in": true
},
"browser": {
"enabled_labs_experiments": [
"brave-compact-horizontal-tabs@1"
]
},
"performance_tuning": {
"battery_saver_mode": {
"state": 0
},
"high_efficiency_mode": {
"state": 2
}
}
}

111
brave_debug.sh Executable file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env bash
#
# Brave Browser configuration backup & diff tool (jq-enhanced)
#
# Usage:
# brave_backup.sh backup [prefix]
# brave_backup.sh diff <prefix>
#
set -euo pipefail
# --- CONFIG ---
BRAVE_DIR="$HOME/Library/Application Support/BraveSoftware/Brave-Browser"
FILES=(
"Local State"
"Default/Preferences"
"Default/Secure Preferences"
)
BACKUP_DIR="/tmp/brave_backups"
# --- FUNCTIONS ---
jq_pretty() {
local src="$1"
local dest="$2"
if jq -e . "$src" >/dev/null 2>&1; then
jq --sort-keys . "$src" >"$dest"
echo " (jq formatted)"
return 0
else
cp "$src" "$dest"
echo " (raw copy - not valid JSON)"
return 1
fi
}
backup_files() {
local prefix="${1:-$(date +%Y%m%d_%H%M%S)}"
local target_dir="${BACKUP_DIR}/${prefix}"
mkdir -p "$target_dir"
echo "🔒 Backing up Brave files to: $target_dir"
for f in "${FILES[@]}"; do
local src="${BRAVE_DIR}/${f}"
local dest="${target_dir}/$(basename "$f")"
if [[ -f "$src" ]]; then
jq_pretty "$src" "$dest"
echo "✅ Backed up (pretty JSON): $f$dest"
else
echo "⚠️ Missing: $src"
fi
done
echo "✅ Backup complete: $target_dir"
}
diff_files() {
local prefix="${1:-}"
if [[ -z "$prefix" ]]; then
echo "❌ Please provide a prefix for diff comparison."
exit 1
fi
local backup_dir="${BACKUP_DIR}/${prefix}"
if [[ ! -d "$backup_dir" ]]; then
echo "❌ Backup not found: $backup_dir"
exit 1
fi
echo "🔍 Comparing current files with backup: $backup_dir"
for f in "${FILES[@]}"; do
local current="${BRAVE_DIR}/${f}"
local backup="${backup_dir}/$(basename "$f")"
if [[ -f "$current" && -f "$backup" ]]; then
echo
echo "=== DIFF for $(basename "$f") ==="
local tmp_current=$(mktemp)
# Try pretty-print; if fails, fall back to raw copy
if ! jq_pretty "$current" "$tmp_current" >/dev/null; then
cp "$current" "$tmp_current"
fi
diff -u "$backup" "$tmp_current" || true
rm -f "$tmp_current"
else
echo "⚠️ Missing file in current or backup for: $(basename "$f")"
fi
done
}
# --- MAIN ---
case "${1:-}" in
backup)
backup_files "${2:-}"
;;
diff)
diff_files "${2:-}"
;;
*)
echo "Usage:"
echo " $0 backup [prefix] # Backup Brave config files (prettified JSON)"
echo " $0 diff <prefix> # Compare with a specific backup"
exit 1
;;
esac

42
brave_set_assets.py Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
import os
import shutil
def copy_tree(src_dir, dst_dir):
"""Recursively copy all files and folders from src_dir to dst_dir."""
for root, dirs, files in os.walk(src_dir):
rel_path = os.path.relpath(root, src_dir)
dest_root = os.path.join(dst_dir, rel_path) if rel_path != "." else dst_dir
os.makedirs(dest_root, exist_ok=True)
for file in files:
src_file = os.path.join(root, file)
dst_file = os.path.join(dest_root, file)
shutil.copy2(src_file, dst_file)
print(f"✔ Copied: {src_file}{dst_file}")
def main():
# Source directory (next to this script)
script_dir = os.path.dirname(os.path.abspath(__file__))
assets_dir = os.path.join(script_dir, "assets")
# Fallback to _assets if assets/ doesn't exist
if not os.path.exists(assets_dir):
alt_assets_dir = os.path.join(script_dir, "_assets")
if os.path.exists(alt_assets_dir):
print(f"⚠️ 'assets' not found, using '_assets' instead.")
assets_dir = alt_assets_dir
else:
print(f"❌ Neither 'assets' nor '_assets' directory found in {script_dir}")
return
# Destination (Brave profile folder)
base_dir = os.path.expanduser("~/Library/Application Support/BraveSoftware/Brave-Browser")
# Copy everything from assets → Brave-Browser
copy_tree(assets_dir, base_dir)
print("\n✅ Brave configuration copied successfully.")
if __name__ == "__main__":
main()

135
brave_set_policies.py Executable file
View File

@ -0,0 +1,135 @@
#!/usr/bin/env python3
import subprocess
import os
import time
import threading
import shutil
BUNDLE_ID = "com.brave.Browser"
# BUNDLE_ID = "com.google.chrome"
MANAGED_PLIST = f"/Library/Managed Preferences/{BUNDLE_ID}.plist"
# === Brave-specific feature lockdown ===
managed_policies = {
# "RestoreOnStartup": 5, # Open new tab on startup
# "HomepageIsNewTabPage": True, # Use New Tab as homepage
"AutofillAddressEnabled": False, # Disable address autofill
"AutofillCreditCardEnabled": False, # Disable credit card autofill
"BackgroundModeEnabled": False, # Disable background tasks
"BookmarkBarEnabled": False, # Disable bookmark bar
"BraveAIChatEnabled": False, # Disable Brave AI features
"BraveNewsDisabled": True, # Disable Brave News
"BravePlaylistEnabled": False, # Disable Brave Playlist
"BraveRewardsDisabled": True, # Disable Brave Rewards
"BraveSpeedreaderEnabled": False, # Disable Speedreader
"BraveStatsPingEnabled": False, # Disable telemetry pings
"BraveTalkDisabled": True, # Disable Brave Talk
"BraveVPNDisabled": True, # Disable Brave VPN
"BraveWalletDisabled": True, # Disable Brave Wallet
"BraveWalletShowInToolbar": False, # Hide wallet icon
"BraveWebDiscoveryEnabled": False, # Disable web discovery
"BrowserSignin": 0, # Disable browser sign-in
"DefaultGeolocationSetting": 2, # Block location access
"EnableMediaRouter": False, # Disable Google Cast
"GoogleSearchSidePanelEnabled": False, # Disable Google Search side panel
"PasswordLeakDetectionEnabled": False, # Disable password leak check
"PasswordManagerEnabled": False, # Disable password manager
"PasswordManagerPasskeysEnabled": False, # Disable passkeys
"PasswordSharingEnabled": False, # Disable password sharing
"PaymentMethodQueryEnabled": False, # Disable payment autofill
"PrivacySandboxAdMeasurementEnabled": False, # Disable Privacy Sandbox ads
"PrivacySandboxAdTopicsEnabled": False, # Disable ad topics
"PrivacySandboxPromptEnabled": False, # Disable Privacy Sandbox prompt
"PrivacySandboxSiteEnabledAdsEnabled": False, # Disable site-enabled ads
"PromotionsEnabled": False, # Disable promotional content
"ShowHomeButton": False, # Hide Home button
"SideSearchEnabled": False, # Disable side search
"SpellcheckEnabled": False, # Disable spellcheck
"SuggestedContentEnabled": False, # Disable content suggestions
"SyncDisabled": True, # Disable Brave Sync
"TorDisabled": True, # Disable Tor integration
"TranslateEnabled": False, # Disable translate feature
"UserFeedbackAllowed": False, # Disable sending feedback
"DeviceMetricsReportingEnabled": False,
# "MetricsReportingEnabled": False,
"SafeBrowsingExtendedReportingEnabled": False,
"DefaultBrowserSettingEnabled": False,
"AlternateErrorPagesEnabled": False,
"URLKeyedAnonymizedDataCollectionEnabled": False,
"SpellCheckServiceEnabled": False,
"SearchSuggestEnabled": False,
"SigninInterceptionEnabled": False
}
def run(cmd, use_sudo=False, silent=False):
if use_sudo:
cmd = ["sudo"] + cmd
if not silent:
print("", " ".join(cmd))
subprocess.run(cmd, check=True)
def keep_sudo_alive():
"""Refresh sudo timestamp every 60 s."""
while True:
subprocess.run(["sudo", "-v"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(60)
def ensure_managed_dir():
os.makedirs("/Library/Managed Preferences", exist_ok=True)
run(["chown", "root:wheel", "/Library/Managed Preferences"], use_sudo=True)
run(["chmod", "755", "/Library/Managed Preferences"], use_sudo=True)
def set_policy(key, value):
# Remove existing key before adding (avoid duplicate errors)
subprocess.run(
["sudo", "/usr/libexec/PlistBuddy", "-c", f"Delete :{key}", MANAGED_PLIST],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
# Choose correct PlistBuddy type
if isinstance(value, bool):
cmd = ["Add", f":{key}", "bool", "true" if value else "false"]
elif isinstance(value, int):
cmd = ["Add", f":{key}", "integer", str(value)]
elif isinstance(value, str):
cmd = ["Add", f":{key}", "string", value]
elif isinstance(value, list):
# array
run(["/usr/libexec/PlistBuddy", "-c", f"Add :{key} array", MANAGED_PLIST], use_sudo=True)
for v in value:
run(["/usr/libexec/PlistBuddy", "-c", f"Add :{key}: string {v}", MANAGED_PLIST], use_sudo=True)
return
else:
print(f"⚠️ Unsupported type for key {key}")
return
run(["/usr/libexec/PlistBuddy", "-c", " ".join(cmd), MANAGED_PLIST], use_sudo=True)
def set_managed_policies():
ensure_managed_dir()
for key, value in managed_policies.items():
set_policy(key, value)
def refresh_preferences():
run(["killall", "cfprefsd"], use_sudo=True)
def cleanup_brave_data():
"""Optionally remove crypto-related folders."""
profile = os.path.expanduser("~/Library/Application Support/BraveSoftware/Brave-Browser/Default")
for folder in ["brave_rewards", "ethereum_wallet", "brave_vpn", "ai_chat"]:
path = os.path.join(profile, folder)
if os.path.exists(path):
print(f"🧹 Removing {path}")
shutil.rmtree(path, ignore_errors=True)
if __name__ == "__main__":
print("🔐 Applying full Brave Browser hardening policies (mandatory).")
subprocess.run(["sudo", "-v"], check=True) # ask for password once
threading.Thread(target=keep_sudo_alive, daemon=True).start()
set_managed_policies()
cleanup_brave_data()
refresh_preferences()
print(f"\n✅ All mandatory policies applied under:\n {MANAGED_PLIST}")
print(" Restart Brave and check brave://policy — all should show Level = Mandatory.")
print(" You can re-run this script anytime to refresh or update settings.")

8
brave_update_assets.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
BR_PATH="$HOME/Library/Application Support/BraveSoftware/Brave-Browser"
cp -r ./_assets ./assets
cp "${BR_PATH}/Local State" ./assets
cp "${BR_PATH}/Default/Preferences" ./assets/Default
cp "${BR_PATH}/Default/Secure Preferences" ./assets/Default

View File

@ -0,0 +1,114 @@
<?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>AlternateErrorPagesEnabled</key>
<false/>
<key>AutofillAddressEnabled</key>
<false/>
<key>AutofillCreditCardEnabled</key>
<false/>
<key>BackgroundModeEnabled</key>
<false/>
<key>BookmarkBarEnabled</key>
<false/>
<key>BraveAIChatEnabled</key>
<false/>
<key>BraveNewsDisabled</key>
<true/>
<key>BravePlaylistEnabled</key>
<false/>
<key>BraveRewardsDisabled</key>
<true/>
<key>BraveSpeedreaderEnabled</key>
<false/>
<key>BraveStatsPingEnabled</key>
<false/>
<key>BraveTalkDisabled</key>
<true/>
<key>BraveVPNDisabled</key>
<true/>
<key>BraveWalletDisabled</key>
<true/>
<key>BraveWalletShowInToolbar</key>
<false/>
<key>BraveWebDiscoveryEnabled</key>
<false/>
<key>BrowserSignin</key>
<integer>0</integer>
<key>DefaultBrowserSettingEnabled</key>
<false/>
<key>DefaultGeolocationSetting</key>
<integer>2</integer>
<key>DeviceMetricsReportingEnabled</key>
<false/>
<key>EnableMediaRouter</key>
<false/>
<key>GoogleSearchSidePanelEnabled</key>
<false/>
<key>PasswordLeakDetectionEnabled</key>
<false/>
<key>PasswordManagerEnabled</key>
<false/>
<key>PasswordManagerPasskeysEnabled</key>
<false/>
<key>PasswordSharingEnabled</key>
<false/>
<key>PaymentMethodQueryEnabled</key>
<false/>
<key>PrivacySandboxAdMeasurementEnabled</key>
<false/>
<key>PrivacySandboxAdTopicsEnabled</key>
<false/>
<key>PrivacySandboxPromptEnabled</key>
<false/>
<key>PrivacySandboxSiteEnabledAdsEnabled</key>
<false/>
<key>PromotionsEnabled</key>
<false/>
<key>SafeBrowsingExtendedReportingEnabled</key>
<false/>
<key>SearchSuggestEnabled</key>
<false/>
<key>ShowHomeButton</key>
<false/>
<key>SideSearchEnabled</key>
<false/>
<key>SigninInterceptionEnabled</key>
<false/>
<key>SpellCheckServiceEnabled</key>
<false/>
<key>SpellcheckEnabled</key>
<false/>
<key>SuggestedContentEnabled</key>
<false/>
<key>SyncDisabled</key>
<true/>
<key>TorDisabled</key>
<true/>
<key>TranslateEnabled</key>
<false/>
<key>URLKeyedAnonymizedDataCollectionEnabled</key>
<false/>
<key>UserFeedbackAllowed</key>
<false/>
<key>ExtensionSettings</key>
<dict>
<key>ghmbeldphafepmbegfdlkpapadhbakde</key>
<dict>
<key>installation_mode</key>
<string>force_installed</string>
<key>update_url</key>
<string>https://clients2.google.com/service/update2/crx</string>
<key>toolbar_pin</key>
<string>force_pinned</string>
</dict>
</dict>
<key>DefaultSearchProviderEnabled</key><true/>
<key>DefaultSearchProviderName</key><string>Brave</string>
<key>DefaultSearchProviderSearchURL</key><string>https://search.brave.com/search?q={searchTerms}</string>
<key>DefaultSearchProviderSuggestURL</key><string>https://search.brave.com/api/suggest?q={searchTerms}</string>
<key>DefaultSearchProviderKeyword</key><string>brave</string>
</dict>
</plist>