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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
72 changes: 46 additions & 26 deletions tests/noise-decoy-stealth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down
Loading