diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8f949..67b152d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S ## [Unreleased] +## [2.5.0] — 2026-08-21 + ### Added - Plain-language **Page not working?** recovery card in the popup for the current site, with one-click **Pause this site and reload** / **Turn protection back on**. A detected compatibility warning upgrades the same card instead of using separate technical copy. @@ -364,7 +366,9 @@ Five toggleable DNR rulesets with per-ruleset `enabled` defaults, file-per-categ - `.editorconfig`, `.prettierrc`, `.prettierignore` for cross-editor consistency. - GitHub Actions: `format.yml` (Prettier auto-format + commit back), `validate.yml` (JSON syntax + DNR rule shape + manifest file-reference checks), `release.yml` (tag-triggered zip + GitHub Release). -[Unreleased]: https://github.com/G-man3207/Static/compare/v2.3.0...HEAD +[Unreleased]: https://github.com/G-man3207/Static/compare/v2.5.0...HEAD +[2.5.0]: https://github.com/G-man3207/Static/compare/v2.4.0...v2.5.0 +[2.4.0]: https://github.com/G-man3207/Static/compare/v2.3.0...v2.4.0 [2.3.0]: https://github.com/G-man3207/Static/compare/v2.2.0...v2.3.0 [2.2.0]: https://github.com/G-man3207/Static/compare/v2.1.0...v2.2.0 [2.1.0]: https://github.com/G-man3207/Static/compare/v2.0.10...v2.1.0 diff --git a/manifest.json b/manifest.json index 50399b3..150e4dc 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Static", - "version": "2.4.0", + "version": "2.5.0", "description": "Blocks extension-enumeration probes and extension-derived browser fingerprinting.", "browser_specific_settings": { "gecko": { diff --git a/package-lock.json b/package-lock.json index 0d29eb8..359d4b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "static", - "version": "2.4.0", + "version": "2.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "static", - "version": "2.4.0", + "version": "2.5.0", "license": "MIT", "devDependencies": { "@eslint/compat": "^2.0.5", diff --git a/package.json b/package.json index 9e58b3e..19fae42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static", - "version": "2.4.0", + "version": "2.5.0", "description": "Chrome/Firefox MV3 extension that blocks extension-enumeration probes and extension-derived fingerprinting.", "private": true, "scripts": { diff --git a/tests/noise-decoy-stealth.spec.js b/tests/noise-decoy-stealth.spec.js index dc8b17e..e96496b 100644 --- a/tests/noise-decoy-stealth.spec.js +++ b/tests/noise-decoy-stealth.spec.js @@ -35,34 +35,60 @@ const JPEG_MAGIC = [0xff, 0xd8, 0xff]; const magicPrefix = (bytes, n) => Array.from(bytes.slice(0, n)); +const waitForDecoy = async (page, url) => { + await expect + .poll( + () => + page.evaluate(async (resourceUrl) => { + try { + const response = await fetch(resourceUrl); + return !!(response && response.ok); + } catch { + return false; + } + }, url), + { timeout: 15_000 } + ) + .toBe(true); +}; + +const openSeededPage = async (extension, server, ids, options) => { + await seedPersona(extension, server.origin, ids, options); + const page = await extension.context.newPage(); + await page.goto(server.url("/blank.html")); + await waitForDecoy(page, probedUrl(ids[0])); + return page; +}; + test("Noise manifests are ID-seeded and stable across fetch and XHR", async ({ extension, server, }) => { - const page = await extension.context.newPage(); - await seedPersona(extension, server.origin, [BITWARDEN, UBLOCK, UNKNOWN_A, UNKNOWN_B], { + const page = await openSeededPage(extension, server, [BITWARDEN, UBLOCK, UNKNOWN_A, UNKNOWN_B], { unknownIds: [UNKNOWN_A, UNKNOWN_B], }); - await page.goto(server.url("/blank.html")); - await page.waitForTimeout(300); const result = await page.evaluate( async (urls) => { const loadManifest = async (url) => { - const response = await fetch(url); - const text = await response.text(); - const xhrBody = await new Promise((resolve) => { - const xhr = new XMLHttpRequest(); - xhr.addEventListener("loadend", () => resolve(xhr.responseText)); - xhr.open("GET", url); - xhr.send(); - }); - return { - fetch: JSON.parse(text), - xhr: JSON.parse(xhrBody), - status: response.status, - ok: response.ok, - }; + try { + const response = await fetch(url); + const text = await response.text(); + const xhrBody = await new Promise((resolve) => { + const xhr = new XMLHttpRequest(); + xhr.addEventListener("loadend", () => resolve(xhr.responseText)); + xhr.open("GET", url); + xhr.send(); + }); + return { + fetch: JSON.parse(text), + xhr: JSON.parse(xhrBody), + status: response.status, + ok: response.ok, + }; + } catch { + return { fetch: null, xhr: null, status: 0, ok: false }; + } }; return { bitwarden: await loadManifest(urls.bitwarden), @@ -116,10 +142,7 @@ test("Noise image fetch decoys match path extension magic bytes and Content-Type extension, server, }) => { - const page = await extension.context.newPage(); - await seedPersona(extension, server.origin, [BITWARDEN]); - await page.goto(server.url("/blank.html")); - await page.waitForTimeout(300); + const page = await openSeededPage(extension, server, [BITWARDEN]); const result = await page.evaluate( async (urls) => { @@ -173,10 +196,7 @@ test("Noise does not reuse one manifest body for every persona ID", async ({ extension, server, }) => { - const page = await extension.context.newPage(); - await seedPersona(extension, server.origin, [BITWARDEN, UBLOCK]); - await page.goto(server.url("/blank.html")); - await page.waitForTimeout(300); + const page = await openSeededPage(extension, server, [BITWARDEN, UBLOCK]); const bodies = await page.evaluate( async (urls) => {