Skip to content

1.2.0: automatic updates and audit fixes - #1

Open
kayurachann wants to merge 4 commits into
mainfrom
feature/auto-update-and-audit-fixes
Open

kayurachann wants to merge 4 commits into
mainfrom
feature/auto-update-and-audit-fixes

Conversation

@kayurachann

@kayurachann kayurachann commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Version 1.2.0 does two things:

  • it adds automatic updates, so an unpacked installation no longer has to be reinstalled by hand;
  • it fixes the defects found in two review rounds: a full audit of the fork (61 findings) and a review of the finished auto-update work (64 findings). Each fix has a regression test.

Automatic updates

  • Update checks. Every installation reads the public GitHub release list about every six hours. A newer version appears in Settings → Updates and in the popup.

    • The request goes to api.github.com. It carries no identifiers and no browsing data.
    • Checks use ETag/304, respect rate limits and retry failures with backoff through a one-shot alarm. They can be turned off, or controlled by the managed autoUpdate policy (off / notify / auto).
    • Development builds and the incognito worker never check. On macOS and Linux the extension notifies only.
  • Optional Windows updater (platform/mv3/updater). The user runs updater\install-updater.cmd once and selects Allow the updater, which grants the new optional nativeMessaging permission. The extension restarts once and reopens the Updates section. From then on each release installs like this:

    1. Download from this repository's Releases.
    2. SHA-256 check. Once a signing key is published, the RSA release signature is checked too. Keys are pinned and versioned by generation, and there is no trust on first use: the updater adopts new keys, and replaces itself, only from a package whose signature verified.
    3. The package must be the same edition with the same manifest key (so the extension ID never changes) and a strictly newer version.
    4. Zip-slip and size limits. The updater refuses folders that contain junctions or symbolic links, protected or shared folders (for example directly under C:\), and folders holding files that the installed package did not list in updater/package-files.json.
    5. Backup of the current version, then robocopy /MIR /IS /IT /XJ /XD _metadata, verification, and restore on failure. An update interrupted mid-copy is undone automatically on the next run, and the dashboard says so.
    6. chrome.runtime.reload(). One-click rollback is available afterwards, and automatic installs then hold the version the user rolled back from.

    The updater needs no administrator rights, no service and no scheduled task. It runs only when Chrome starts it. Updates wait (up to 60 s) for running filter-list transactions instead of interrupting them.

  • Release workflow (.github/workflows/release.yml). Pushing a v<version> tag runs two jobs:

    1. a read-only build job: npm ci --ignore-scripts, tests, lint, both builds and their validators, and version and packaging checks for each ZIP;
    2. a publish job: signs the packages when a key is configured, checks that the previous release's keys still verify them, attests build provenance and publishes the assets. Assets that already exist must be byte-identical.
  • Design, protocol, trust model and troubleshooting are in docs/AUTO-UPDATE.md. PRIVACY, THREAT-MODEL, ARCHITECTURE, ROADMAP, CHANGELOG and all 10 READMEs are updated.

Audit fixes

Highlights from the first audit (61 findings, 60 fixed, 1 partly):

  • Startup: a ruleset journal left by an older package no longer bricks worker startup. This matters for updates.
  • Cosmetic filtering: site-specific cosmetic filtering works on a fresh profile. Before, it did nothing until a filtering mode was changed.
  • Filter Store: pinned lists downloaded over the network no longer always fail, and scheduled refreshes keep the last-good cache.
  • Firewall: native rule counts no longer multiply per learned domain (the cause of the 4,096-rule cap and stalls).
  • Popup blocker:
    • sign-in and checkout windows are no longer closed when they change URL within the same site;
    • the matcher budget is no longer exhausted by long ad URLs;
    • popup-blocker-02 is only partly fixed: a popup that moves to an unrelated host more than 5 s after opening is still re-judged.
  • Element tools: the picker and unpicker bind to the real page host, and the worker enforces this too.
  • Administrator locks: filteringMode locks are enforced in the worker, not only in the UI.
  • Dashboard: backup, restore and reset errors are shown in the page, and restore preflights DNR syntax.
  • Performance: origin-only scriptlet copies compile lazily (628 KiB file: 6.8 ms → 4.0 ms per frame).

Highlights from the review of the auto-update work (64 findings):

  • robocopy /MIR could follow junctions or delete user files in the extension folder. Links, shared folders and unlisted files are now refused, and nothing is changed.
  • A rollback was undone by the next automatic check. The rolled-back version is now held.
  • Update bookkeeping started only after start(), so a broken start could block the fix. It now starts with the worker.
  • Self-update and key adoption read from the writable folder after the copy. They now use the verified staging copy, only when signed.
  • Chrome's permission grant raced the updater restart and reported "A filter list update is running". The restart now waits for queued transactions.

The full list is in CHANGELOG.md.

Build and tests

  • npm test now discovers every tools/test-*.mjs through tools/run-tests.mjs, and npm run lint covers every tool.
  • npm run lint:all passes; it previously failed with 136 errors.
  • The experimental-build test works without PowerShell 7.
  • READMEs no longer pin a version-specific download link, and a test prevents such links from returning.

Testing

  • npm test: 64/64 test programs pass. New suites:

    • test-auto-update covers update policy and orchestration.
    • test-updater-host drives the real PowerShell updater against a loopback release server: protocol, hostile packages, lock, stage/apply/rollback, interrupted-apply repair, folder permissions and links, unexpected files, signatures, key generations and rotation, self-update and uninstall.
    • test-release-signing and test-release-workflow cover the signing tool and the workflow.
    • One regression test per fix.
  • npm run lint and npm run lint:all are clean.

  • Standard and Experimental 1.2.0 builds pass validate-mv3 --release (55 rulesets, 70,314 DNR rules).

  • tools/test-auto-update-chrome.mjs ran on real Google Chrome 153 on Windows. It passed 6 times in a row on the final code (twice on the final build):

    1. the packaged installer computed Chrome's unpacked extension ID;
    2. Allow the updater was granted through Chrome's real permission dialog;
    3. the extension restarted once and reopened with the Updates section focused;
    4. Check now downloaded and verified a 31.8 MB package and applied 1.2.0 → 1.2.0.1 in 8–10 s, keeping extension storage and 6 enabled rulesets;
    5. Restore version rolled back to 1.2.0 and removed the newer version's files.

    The test harness now clicks Chrome's permission dialog only after Chrome's input protection allows it, and times out instead of hanging on a closed worker session. These runs found defects that unit tests could not catch: the native messaging binding is missing after a runtime permission grant; PowerShell unrolls a one-element allowed_origins array into a string; the permission grant raced the restart; and the dashboard focused the Updates heading while the page was still hidden.

    CI on this PR found one more, which affects users too: Windows PowerShell started from PowerShell 7 (a GitHub Actions step, or a PowerShell 7 terminal running the one-step installer) inherits a module path from which it cannot load its own modules, so Get-Acl, Get-FileHash and Test-Path went missing and the updater refused every folder. The updater and installer now load Windows PowerShell's own modules first, and test-updater-host runs every PowerShell process with unloadable copies of those modules first on the path. The test also runs correctly with the 8.3 TEMP path of GitHub runners.

After merging

  1. Tag v1.2.0 to publish the release through the new workflow.
  2. Existing users install 1.2.0 manually one last time (into a new empty folder such as %LOCALAPPDATA%\uBlockPlus\Extension), then run updater\install-updater.cmd.
  3. Optional: enable release signing with node tools/release-signing.mjs generate --out <outside the repo>, then commit the public key and set the UBP_RELEASE_SIGNING_KEY secret.

🤖 Generated with Claude Code

kayurachann and others added 4 commits September 24, 2026 03:06
Automatic updates:
- Every installation checks the GitHub release list about every six
  hours (ETag, rate limits, backoff retry, preview/stable channels,
  managed autoUpdate policy) and shows new versions in Settings and the
  popup. Development builds and the incognito worker do not check.
- Optional Windows updater (platform/mv3/updater): a native messaging
  host installed per user with install-updater.cmd. It verifies the
  SHA-256 file and, once keys are published, RSA release signatures
  against pinned, versioned keys (no trust on first use; self-update
  and key adoption only from signed packages). It refuses unsafe or
  shared folders, links and unlisted files, backs up, applies with
  robocopy, restores on failure, repairs interrupted applies, and
  supports rollback with a hold on the version rolled back from.
- The update manager starts with the worker, independently of start(),
  and waits (bounded) for filtering transactions before replacing the
  folder or restarting.
- Release workflow split into a read-only build job and a publish job
  that signs, attests build provenance and publishes.

Audit fixes: 61 findings from the fork audit and 64 from the review of
the auto-update work, each with a regression test. Highlights: startup
no longer bricks on an old ruleset journal, cosmetic filtering on a
fresh profile, Filter Store pinned downloads and refresh, firewall rule
counts, popup-blocker same-site navigation and matcher budget, element
tool host binding, worker-side administrator locks, dashboard error
reporting, and lazy scriptlet compilation.

Tests and tooling: tools/run-tests.mjs runs every tools/test-*.mjs;
lint covers every tool and lint:all is clean; a real-Chrome end-to-end
test (tools/test-auto-update-chrome.mjs) covers install, permission
grant, restart, update and rollback.

Docs: new docs/AUTO-UPDATE.md; README (all 10 languages), PRIVACY,
THREAT-MODEL, ARCHITECTURE, ROADMAP, FEATURE-MATRIX and CHANGELOG
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows PowerShell started from PowerShell 7 (a PowerShell 7 terminal,
or a GitHub Actions step) inherits a module path from which
Microsoft.PowerShell.Security cannot load, so Get-Acl is missing. The
updater then refused every extension folder with "Could not read the
permissions", including in the one-step installer.

The updater now reads access rules through .NET in Windows PowerShell
(Get-Acl only under PowerShell 7) and includes the underlying reason
when permissions cannot be read. test-updater-host runs every
PowerShell process with such a module path and checks that it hides
Get-Acl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub's Windows runners use an 8.3 TEMP (C:\Users\RUNNER~1\...). The
installer registers the ID of the folder's long path, as Chrome derives
it from its folder picker, but the test expected the ID of the short
path. The fixture now uses its long path, and the first installation
passes the folder as TEMP spells it, so the short-to-long mapping stays
covered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Get-Acl was only the first casualty: Windows PowerShell started from
PowerShell 7 (a PowerShell 7 terminal, a GitHub Actions step) inherits
a module path from which Get-FileHash, Test-Path and other built-in
cmdlets cannot load either. The updater and its installer now put
$PSHOME\Modules first before running any cmdlet.

test-updater-host now puts unloadable Security, Utility and Management
modules first on the module path of every PowerShell process, and
checks that they hide Get-Acl and Get-FileHash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant