Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ jobs:
shell: pwsh
run: ./scripts/Test-Behavior.ps1

- name: Install PowerShell test tools
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module Pester -RequiredVersion 5.7.1 -Scope CurrentUser -Force -SkipPublisherCheck
Install-Module PSScriptAnalyzer -RequiredVersion 1.24.0 -Scope CurrentUser -Force

- name: Run Pester tests
shell: pwsh
run: Invoke-Pester -Path ./tests -CI

- name: Run PSScriptAnalyzer
shell: pwsh
run: |
$results = foreach ($path in @('./Invoke-BraveDebloat.ps1', './scripts', './src')) {
Invoke-ScriptAnalyzer -Path $path -Recurse -Severity Error
}
if ($results) {
$results | Format-Table -AutoSize | Out-String | Write-Error
exit 1
}

- name: Dry-run extreme preset
shell: pwsh
run: ./Invoke-BraveDebloat.ps1 -Preset Extreme -LockShields
Expand Down
21 changes: 20 additions & 1 deletion Invoke-BraveDebloat.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ param(
[ValidateSet('Auto', 'Windows', 'macOS', 'Linux', 'Android', 'iOS')]
[string]$Platform = 'Auto',

[ValidateSet('Stable', 'Beta', 'Nightly')]
[string]$Channel = 'Stable',

[ValidateSet('CurrentUser', 'LocalMachine')]
[string]$Scope = 'CurrentUser',

Expand Down Expand Up @@ -37,6 +40,14 @@ param(

[string]$UndoFromBackup,

[switch]$ListBackups,

[ValidateRange(-1, 36500)]
[int]$PruneBackupsOlderThanDays = -1,

[ValidateRange(-1, 100000)]
[int]$KeepLatestBackups = -1,

[switch]$List,

[switch]$ListFeatures,
Expand Down Expand Up @@ -66,11 +77,16 @@ foreach ($moduleName in @('Common.ps1', 'Manifest.ps1', 'PlatformPolicy.ps1', 'B
$manifest = Get-Manifest
$platformName = Resolve-PlatformName -Name $Platform
if ([string]::IsNullOrWhiteSpace($ProfileRoot)) {
$ProfileRoot = Get-DefaultProfileRoot -PlatformName $platformName
$ProfileRoot = Get-DefaultProfileRoot -PlatformName $platformName -Channel $Channel
}
$applyChanges = $Apply -and -not $WhatIfPreference
$isWhatIf = $Apply -and $WhatIfPreference

if ($ListBackups -or $PruneBackupsOlderThanDays -ge 0 -or $KeepLatestBackups -ge 0) {
Invoke-BackupRetention -Directory $BackupDirectory -OlderThanDays $PruneBackupsOlderThanDays -KeepLatest $KeepLatestBackups -DoApply:$applyChanges
return
}

if ($UndoFromBackup) {
Restore-RegistryBackup -BackupPath $UndoFromBackup -Manifest $manifest -ProfileRoot $ProfileRoot -AllowedPolicyPath $PolicyPath -DoApply:$applyChanges
if (-not $applyChanges) {
Expand Down Expand Up @@ -197,6 +213,9 @@ else {
Write-Step "Preset: $Preset"
}
Write-Step "Platform: $platformName"
if ($Channel -ne 'Stable') {
Write-Step "Channel: $Channel"
}
Write-Step "Scope: $Scope ($($policyTarget.Path))"
if ($LockShields) {
Write-Step 'Shield baseline: enabled. Brave will keep ad blocking, standard fingerprinting protection, HTTPS upgrades, and referrer capping on by policy.'
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ Extra UI in the `Extreme` preset:

Optional profile preference cleanup can also hide some new tab, sponsored background, and toolbar surfaces. That part edits per-profile `Preferences` JSON, so close Brave before applying it.

## Install

Download the latest release from the repository's Releases page, then extract the zip to a folder you control, such as `Downloads\BraveDebloater`.

Open PowerShell in that folder and run the default dry-run:

```powershell
.\Invoke-BraveDebloat.ps1
```

Review the output before applying changes. To verify a release archive first, download `SHA256SUMS.txt` from the same release and compare it with:

```powershell
Get-FileHash .\BraveDebloater-vX.Y.Z.zip -Algorithm SHA256
```

## Start Here

Preview the default cleanup first:
Expand Down Expand Up @@ -83,6 +99,16 @@ Run a read-only health check:
.\Invoke-BraveDebloat.ps1 -Doctor
```

List backups or preview retention cleanup:

```powershell
.\Invoke-BraveDebloat.ps1 -ListBackups
.\Invoke-BraveDebloat.ps1 -PruneBackupsOlderThanDays 30
.\Invoke-BraveDebloat.ps1 -KeepLatestBackups 10
```

Add `-Apply` only after the preview lists the backups you expect to delete.

Apply the default cleanup and lock a safe Shields baseline:

```powershell
Expand Down Expand Up @@ -131,6 +157,17 @@ Examples:

Use `-PolicyPath` when testing, or when your managed Linux/macOS policy file lives somewhere custom.

## Brave Channels

Profile preference cleanup targets Brave Stable by default. Use `-Channel` when you want the default profile path for Beta or Nightly:

```powershell
.\Invoke-BraveDebloat.ps1 -Channel Beta -IncludeProfilePreferences
.\Invoke-BraveDebloat.ps1 -Channel Nightly -IncludeProfilePreferences
```

Stable policy behavior is unchanged. `-ProfileRoot` still overrides the detected profile path.

## Presets

`Standard` removes Brave-specific bloat and Brave telemetry.
Expand Down Expand Up @@ -214,6 +251,18 @@ Policy names and values come from Brave's official Group Policy documentation an

See `docs/debloatable-validation.md` for the source version, the policy choices, and the validation commands.

See `ROADMAP.md` for planned safety, testing, release trust, user experience, and maintainability work.

## Release Checksums

Generate SHA256 checksums for release artifacts before publishing:

```powershell
.\scripts\New-ReleaseChecksums.ps1 -Path .\BraveDebloater-vX.Y.Z.zip -OutputPath .\SHA256SUMS.txt
```

Upload `SHA256SUMS.txt` beside the release archive.

## Project Checks

Run the local checks:
Expand All @@ -229,6 +278,12 @@ Validate against a downloaded Brave policy template zip:
.\scripts\Test-LatestPolicyTemplates.ps1 -TemplateZipPath .\policy_templates.zip
```

Update the recorded template version after downloading a newer official zip:

```powershell
.\scripts\Update-PolicyTemplateVersion.ps1 -TemplateZipPath .\policy_templates.zip
```

## Pull Request Review

Greptile review guidance lives in `greptile.json`. It covers PowerShell compatibility, policy writes, registry writes, profile JSON writes, and feature-toggle behavior.
33 changes: 33 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Roadmap

BraveDebloater stays safety-first: dry-run by default, native policy writes, strict restore validation, and no policies that weaken Brave Shields, Safe Browsing, updates, or installed extensions.

## Safety

- Keep backup and restore validation narrow.
- Keep profile preference cleanup opt-in and skipped while Brave is running.
- Add policy changes only when they are documented by Brave or Chromium policy sources.

## Testing

- Keep manifest, behavior, Pester, and PowerShell syntax checks in CI.
- Expand tests when new CLI switches change write behavior.
- Validate policy names against Brave's latest templates before releases.

## Release Trust

- Publish SHA256 checksum files beside release archives.
- Keep release notes short and focused on user-visible changes.
- Document how to verify downloads before applying changes.

## User Experience

- Improve examples for Brave Stable, Beta, and Nightly profiles.
- Keep output concise: what would change, what changed, and what to do next.
- Accept user-provided screenshots for common dry-run and Doctor workflows.

## Maintainability

- Keep `Invoke-BraveDebloat.ps1` as a thin entrypoint.
- Put shared behavior in `src/*.ps1`.
- Prefer small issue-sized changes over broad rewrites.
2 changes: 1 addition & 1 deletion config/policies.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"policyTemplateVersion": "150.1.93.96",
"policyTemplateVersion": "150.1.94.4",
"sources": [
"https://support.brave.app/hc/en-us/articles/360039248271-Group-Policy",
"https://brave-browser-downloads.s3.brave.com/latest/policy_templates.zip"
Expand Down
5 changes: 3 additions & 2 deletions docs/debloatable-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ Official sources used for this pass:

Downloaded template evidence:

- Template version: `150.1.93.96`
- Template version: `150.1.94.4`
- Archive timestamp: June 23, 2026
- Checked files: `VERSION` and `windows/admx/brave.admx`

Targeted Reddit, Brave Community, and GitHub searches did not produce a newer or more authoritative debloatable-policy source than Brave's Help Center and template zip.

## What Changed

The manifest version in `config/policies.json` changed from `148.1.91.121` to `150.1.93.96`.
The manifest version in `config/policies.json` changed from `148.1.91.121` to `150.1.94.4`.

These official-template policies were added because they match BraveDebloater's scope:

Expand Down Expand Up @@ -82,3 +82,4 @@ pwsh -NoProfile -File ./scripts/Test-Behavior.ps1
```

The template validator uses a local zip file on purpose. CI can download the current zip before running it, but normal offline checks do not need network access.

19 changes: 19 additions & 0 deletions scripts/New-ReleaseChecksums.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#requires -Version 5.1
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string[]]$Path,

[string]$OutputPath = 'SHA256SUMS.txt'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$lines = foreach ($item in $Path) {
$hash = Get-FileHash -LiteralPath $item -Algorithm SHA256
'{0} {1}' -f $hash.Hash.ToLowerInvariant(), (Split-Path -Leaf $hash.Path)
}

$lines | Set-Content -LiteralPath $OutputPath -Encoding ASCII
Write-Host "Wrote $OutputPath"
21 changes: 21 additions & 0 deletions scripts/PolicyTemplateVersion.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#requires -Version 5.1

function Get-PolicyTemplateVersionFromText {
param([Parameter(Mandatory = $true)][string]$VersionText)

$values = @{}
foreach ($line in ($VersionText -split "`n")) {
if ($line.Trim() -match '^(MAJOR|MINOR|BUILD|PATCH)=(.+)$') {
$values[$Matches[1]] = $Matches[2].Trim()
}
}

$keys = @('MAJOR', 'MINOR', 'BUILD', 'PATCH')
foreach ($key in $keys) {
if (-not $values.ContainsKey($key)) {
throw 'Template VERSION file did not contain MAJOR, MINOR, BUILD, and PATCH.'
}
}

return (($keys | ForEach-Object { $values[$_] }) -join '.')
}
28 changes: 28 additions & 0 deletions scripts/Test-Behavior.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,34 @@ try {
throw '-WhatIf created a backup directory.'
}

$channelOutput = (& $scriptPath -Preset Core -Channel Beta -ProfileRoot '' *>&1 | Out-String)
Assert-TextContains -Text $channelOutput -Expected 'Channel: Beta' -Context '-Channel Beta output'

$retentionDirectory = Join-Path $tempRoot 'RetentionBackups'
New-Item -ItemType Directory -Path $retentionDirectory -Force | Out-Null
$oldBackup = Join-Path $retentionDirectory 'BraveDebloater-20240101-010101-001.json'
$newBackup = Join-Path $retentionDirectory 'BraveDebloater-20260101-010101-001.json'
Set-Content -LiteralPath $oldBackup -Value '{}' -Encoding UTF8
Set-Content -LiteralPath $newBackup -Value '{}' -Encoding UTF8
(Get-Item -LiteralPath $oldBackup).LastWriteTime = (Get-Date).AddDays(-60)
(Get-Item -LiteralPath $newBackup).LastWriteTime = Get-Date

$listBackupsOutput = (& $scriptPath -BackupDirectory $retentionDirectory -ListBackups *>&1 | Out-String)
Assert-TextContains -Text $listBackupsOutput -Expected 'Backups: 2 found' -Context '-ListBackups output'
Assert-TextDoesNotContain -Text $listBackupsOutput -Unexpected 'Backup cleanup: nothing to remove.' -Context '-ListBackups output'

$retentionPreview = (& $scriptPath -BackupDirectory $retentionDirectory -PruneBackupsOlderThanDays 30 *>&1 | Out-String)
Assert-TextContains -Text $retentionPreview -Expected 'Would remove backup BraveDebloater-20240101-010101-001.json' -Context 'backup retention preview'
if (-not (Test-Path -LiteralPath $oldBackup)) {
throw 'Backup retention preview deleted a backup.'
}

$retentionApply = (& $scriptPath -BackupDirectory $retentionDirectory -KeepLatestBackups 1 -Apply *>&1 | Out-String)
Assert-TextContains -Text $retentionApply -Expected 'Removed backup BraveDebloater-20240101-010101-001.json.' -Context 'backup retention apply'
if (Test-Path -LiteralPath $oldBackup) {
throw 'Backup retention apply did not remove the old backup.'
}

$tamperedBackup = Join-Path $tempRoot 'tampered-backup.json'
[ordered]@{
schemaVersion = 1
Expand Down
5 changes: 3 additions & 2 deletions scripts/Test-LatestPolicyTemplates.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$manifestPath = Join-Path (Join-Path $root 'config') 'policies.json'

. (Join-Path $PSScriptRoot 'PolicyTemplateVersion.ps1')

Add-Type -AssemblyName System.IO.Compression.FileSystem

function Read-ZipEntryText {
Expand Down Expand Up @@ -47,8 +49,7 @@ $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
$zip = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path -LiteralPath $TemplateZipPath))
try {
$versionText = Read-ZipEntryText -Zip $zip -EntryName 'VERSION'
$templateVersion = (($versionText -split "`n") | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^(MAJOR|MINOR|BUILD|PATCH)=' }) -replace '^[^=]+='
$templateVersion = $templateVersion -join '.'
$templateVersion = Get-PolicyTemplateVersionFromText -VersionText $versionText
if ($templateVersion -ne [string]$manifest.policyTemplateVersion) {
throw "Manifest policyTemplateVersion '$($manifest.policyTemplateVersion)' does not match template '$templateVersion'."
}
Expand Down
33 changes: 33 additions & 0 deletions scripts/Test-PolicyManifest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,37 @@ function Resolve-Preset {
return $items.ToArray()
}

function Test-PolicyTemplateVersionUpdater {
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('BraveDebloaterTemplateVersion-{0}' -f [guid]::NewGuid().ToString('N'))
try {
New-Item -ItemType Directory -Path (Join-Path $tempRoot 'scripts') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $tempRoot 'config') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $tempRoot 'docs') -Force | Out-Null
Copy-Item -LiteralPath (Join-Path $root 'scripts/Update-PolicyTemplateVersion.ps1') -Destination (Join-Path $tempRoot 'scripts/Update-PolicyTemplateVersion.ps1')
Copy-Item -LiteralPath (Join-Path $root 'scripts/PolicyTemplateVersion.ps1') -Destination (Join-Path $tempRoot 'scripts/PolicyTemplateVersion.ps1')
Set-Content -LiteralPath (Join-Path $tempRoot 'config/policies.json') -Value '{"policyTemplateVersion": "0.0.0.0"}' -Encoding UTF8
Set-Content -LiteralPath (Join-Path $tempRoot 'docs/debloatable-validation.md') -Value 'Template version: `0.0.0.0`' -Encoding UTF8

$zipRoot = Join-Path $tempRoot 'zip'
New-Item -ItemType Directory -Path $zipRoot -Force | Out-Null
Set-Content -LiteralPath (Join-Path $zipRoot 'VERSION') -Value "PATCH=4`nBUILD=3`nMAJOR=1`nMINOR=2" -Encoding UTF8
$zipPath = Join-Path $tempRoot 'template.zip'
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory($zipRoot, $zipPath)

& (Join-Path $tempRoot 'scripts/Update-PolicyTemplateVersion.ps1') -TemplateZipPath $zipPath *> $null
$updated = Get-Content -LiteralPath (Join-Path $tempRoot 'config/policies.json') -Raw | ConvertFrom-Json
if ([string]$updated.policyTemplateVersion -ne '1.2.3.4') {
throw 'Update-PolicyTemplateVersion.ps1 did not assemble shuffled VERSION keys by name.'
}
}
finally {
if (Test-Path -LiteralPath $tempRoot) {
Remove-Item -LiteralPath $tempRoot -Recurse -Force
}
}
}

if (-not (Test-Path -LiteralPath $manifestPath)) {
throw "Missing manifest: $manifestPath"
}
Expand Down Expand Up @@ -187,4 +218,6 @@ if ($parseErrors.Count -gt 0) {
throw "PowerShell parse errors in Invoke-BraveDebloat.ps1: $($messages -join '; ')"
}

Test-PolicyTemplateVersionUpdater

Write-Host 'Policy manifest and PowerShell syntax checks passed.'
Loading
Loading