Skip to content

Upgrade to Manifest V3 + bypass/unlock improvements - #52

Open
momenbasel wants to merge 3 commits into
Ibit-to:masterfrom
momenbasel:mv3-upgrade
Open

Upgrade to Manifest V3 + bypass/unlock improvements#52
momenbasel wants to merge 3 commits into
Ibit-to:masterfrom
momenbasel:mv3-upgrade

Conversation

@momenbasel

@momenbasel momenbasel commented May 27, 2026

Copy link
Copy Markdown

Summary

  1. Port to Manifest V3 so the extension can be loaded again in current Chrome (MV2 unpacked loads now fail with Cannot install extension because it uses an unsupported manifest version).
  2. Forward-port the XSS fix from Prevent XSS #4 to the current u.js. The current regex capture is not safe to interpolate into HTML.
  3. More bypasses / more unlock, in the spirit of the extension.

google-unlocked.user.js (userscript path) is unchanged.


1. MV3 conversion

  • manifest_version: 2 -> 3
  • browser_action -> action
  • Host match patterns moved out of permissions into host_permissions
  • Removed webRequest + webRequestBlocking (no longer available to non-policy MV3 extensions) and the b.js background script that used them
  • Replaced with a declarativeNetRequest static ruleset (rules.json) that sets Access-Control-Allow-Origin: * on lumendatabase.org responses - exactly what b.js did, so u.js can XHR Lumen pages from the Google origin
  • Uses declarativeNetRequestWithHostAccess so the rule only applies when the extension already has host permission for both initiator (google.*) and target (lumendatabase.org)
  • Dropped the no-op content_security_policy (its value equals the MV3 default)
  • Dropped unused activeTab permission
  • version: 1.5 -> 2.1

2. XSS fix (forward-port of #4)

The current code interpolated the regex capture from the Lumen response directly into href and link text:

l.append('<div class="g"><a href="http://' + i[1] + '" ...>' + i[1] + ' ...');

i[1] is captured by class="infringing_url">([^\s-<]+) - that class does not exclude ", > or &, so a crafted Lumen notice can break out of the href attribute and inject an event handler. All Lumen-derived strings now go through an escapeHTML helper before insertion, and links get rel="noopener noreferrer".

3. New bypasses

Auto-unlock SERP query params (once per query, tombstoned)

On every Google search page load that has a q= and no gu_unlocked sentinel, we redirect once to the same URL with:

  • filter=0 - show the "omitted similar results" Google hides by default
  • pws=0 - turn off personalized results
  • nfpr=1 - turn off Google's silent "search instead for ..." query rewrite

A gu_unlocked=1 sentinel is appended so we never redirect twice on the same navigation. The user can still remove any of these params manually and we will not re-add them.

"Web only" pill (udm=14)

Injects a small pill into the SERP nav linking to the same query with udm=14, Google's plain web-results mode. One-click way to skip the AI Overview / SGE / cards block at the top.

Archive fallbacks in the unlocked panel

Each revealed domain in the Lumen panel now also exposes [Wayback] (web.archive.org/web/*/<host>) and [archive.ph] links, so the user can actually read the takendown page. This is the real end-user payoff of knowing what was removed.

Legacy chillingeffects.org support (idea from #50)

Older Google takedown footers still link to chillingeffects.org/notice.cgi (Lumen's previous name). u.js now also matches those, a second DNR rule injects Access-Control-Allow-Origin: * on chillingeffects responses, and the host is added to host_permissions.

CORS preflight hardening (idea from #49)

DNR rules also set Access-Control-Allow-Methods: GET, OPTIONS so a future preflight (if jQuery ever sends a custom header) does not fail.

How the MV2 -> MV3 CORS rewrite maps

Before (b.js, removed):

chrome.webRequest.onHeadersReceived.addListener(details => {
    details.responseHeaders.push({name: 'Access-Control-Allow-Origin', value: '*'})
    return {responseHeaders: details.responseHeaders}
}, {urls: ["https://lumendatabase.org/*"]}, ["blocking", "responseHeaders", "extraHeaders"]);

After (rules.json):

{
  "id": 1,
  "priority": 1,
  "action": {
    "type": "modifyHeaders",
    "responseHeaders": [
      { "header": "Access-Control-Allow-Origin",  "operation": "set", "value": "*" },
      { "header": "Access-Control-Allow-Methods", "operation": "set", "value": "GET, OPTIONS" }
    ]
  },
  "condition": {
    "urlFilter": "||lumendatabase.org/",
    "resourceTypes": ["xmlhttprequest", "sub_frame", "main_frame"]
  }
}

operation: set is a small correctness improvement over the old code, which used responseHeaders.push(...) and could produce a duplicate Access-Control-Allow-Origin header (browsers reject multi-valued ACAO).

Validation done locally

  • manifest.json + rules.json parsed and shape-checked against the MV3 schema (3 + 2 fields each)
  • u.js passes node --check
  • addons-linter reports zero MV3-relevant manifest errors. (The one error it emits is ADDON_ID_REQUIRED, which is Mozilla-AMO-only - Firefox's browser_specific_settings.gecko.id - and unrelated to Chrome MV3. Pre-existing icon-size warnings on 16.png / 32.png / 48.png are upstream, not introduced by this PR.)

Test plan

  • manifest.json and rules.json are well-formed and schema-clean for MV3
  • Forward-ported XSS escape covers &, ", ', <, > on every Lumen-derived insertion point
  • All original host patterns preserved verbatim in host_permissions
  • Maintainer: chrome://extensions -> Load unpacked -> no manifest errors (Chrome blocks --load-extension on Stable via enterprise policy, so this needs a real install)
  • Maintainer: search a DMCA-affected query on www.google.com; takedown notice links resolve and the panel renders, now with [Wayback] / [archive.ph]
  • Maintainer: confirm the Web only pill appears next to the SERP nav and links to &udm=14
  • Maintainer: verify on Edge / Brave / Opera if part of release matrix

Closes the MV3 deprecation gap and supersedes #49 / #50 / #51 (which were partial MV3 attempts).

Chrome stopped accepting MV2 extensions (Chrome 127+ phases out MV2,
unpacked MV2 loads now fail with "unsupported manifest version").
This change ports the extension to MV3 with no user-facing behavior
changes.

Conversion details:
- manifest_version: 2 -> 3
- browser_action -> action
- permissions split: API perms stay in `permissions`, host patterns
  move to `host_permissions`
- Removed webRequest / webRequestBlocking (no longer available to
  non-policy extensions in MV3) and the background script that used
  them. Replaced with a declarativeNetRequest static ruleset
  (rules.json) that sets `Access-Control-Allow-Origin: *` on
  responses from lumendatabase.org, which is what b.js did
- declarativeNetRequestWithHostAccess is used so the rule only fires
  when the extension already has host permission for both initiator
  (google.*) and target (lumendatabase.org)
- Dropped the no-op `content_security_policy` (its value matched the
  MV3 default)
- Dropped unused `activeTab` permission
- version bumped 1.5 -> 2.0 to reflect the architectural change

Loads cleanly as an unpacked extension on current Chrome / Chromium
and on Edge; userscript path (google-unlocked.user.js) is unchanged.
Copilot AI review requested due to automatic review settings May 27, 2026 02:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR migrates the browser extension to Manifest V3 and replaces the MV2 webRequest-based CORS header modification with a declarativeNetRequest ruleset.

Changes:

  • Upgrade manifest.json from MV2 to MV3 (browser_actionaction, permissions model changes).
  • Replace the background webRequest listener with a static declarative_net_request ruleset.
  • Add rules.json to set Access-Control-Allow-Origin for lumendatabase.org responses.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
extension/rules.json Adds a DNR ruleset to modify response headers for lumendatabase.org.
extension/manifest.json Migrates to MV3 and wires up the DNR rules resource + new permissions model.
extension/b.js Removes MV2 webRequest header modification background script.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread extension/manifest.json
Comment thread extension/rules.json Outdated
Comment thread extension/manifest.json
Comment thread extension/rules.json
Comment thread extension/manifest.json
…I-free mode

Adds several things on top of the MV3 conversion that match the
spirit of the extension (surfacing content Google hides). All
changes are content-script / DNR only - no new dangerous permissions.

XSS fix (forwards-port of upstream PR Ibit-to#4 to current u.js):
- Lumen-derived strings are now HTML-escaped before being inserted
  into the result panel. The previous code interpolated the regex
  capture directly into the href and link text, which lets a
  maliciously crafted Lumen notice break out of the href attribute
  (the capture class `[^\s-<]+` does NOT exclude `"`, `>` or `&`).

SERP unlock (auto, once per query, with sentinel to avoid loops):
- filter=0    -> show "omitted similar" results Google hides by default
- pws=0       -> disable personalized results
- nfpr=1      -> disable Google's "did you mean / search instead for"
                 silent query rewrite
A `gu_unlocked=1` tombstone is appended so the redirect happens at
most once per navigation - the user can still remove any param
manually and we won't re-add it.

"Web only" toggle:
- Injects a small pill into the SERP nav linking to the same query
  with udm=14, which is Google's plain-web mode (no AI Overview,
  no SGE, no Discover-style cards). One-click escape hatch.

Archive fallbacks in the unlocked panel:
- Each revealed domain now also exposes [Wayback] and [archive.ph]
  links so the user can read the takendown page even if it is gone
  from the live web. This is the actual end-user value of knowing
  what was removed.

Legacy chillingeffects.org support (idea from PR Ibit-to#50):
- Old Google takedown footers sometimes still link to
  chillingeffects.org/notice.cgi (Lumen's previous name). u.js now
  also matches those, and a second DNR rule plus host_permissions
  entry lets the cross-origin XHR through.

CORS preflight hardening (idea from PR Ibit-to#49):
- DNR rules also set Access-Control-Allow-Methods: GET, OPTIONS so
  if the browser ever issues a preflight for these requests (e.g.
  if a future jQuery sends a custom header) it does not fail.

version 2.0 -> 2.1
@momenbasel momenbasel changed the title Upgrade extension to Manifest V3 Upgrade to Manifest V3 + bypass/unlock improvements May 27, 2026
The Access-Control-Allow-Origin: * header is now only applied to
requests initiated from the 188 Google search domains the content
script actually runs on, instead of to every page on the web.

Also drops main_frame/sub_frame from resourceTypes: ACAO and
Access-Control-Allow-Methods only affect CORS-mode fetches, so they
were inert on navigations. The extension's only network call is the
jQuery $.ajax in u.js, which is an xmlhttprequest.

Claude-Session: https://claude.ai/code/session_01RMgh2XsVRBrwkd1Xn1FUZ7
@momenbasel

momenbasel commented Jul 27, 2026

Copy link
Copy Markdown
Author

All five threads answered: one code change, four rebuttals.

Changed in 1280be4 - the DNR rules are scoped with initiatorDomains for the 188 Google domains from content_scripts[0].matches, so Access-Control-Allow-Origin: * no longer reaches arbitrary pages. main_frame/sub_frame are dropped, since CORS headers do nothing on navigations.

Left alone, with reasons on the threads: declarativeNetRequestWithHostAccess is an alternative to declarativeNetRequest, not a supplement; the Google host_permissions are required for the request's initiator; the subdomain wildcard is already declared; and https fails TLS on ibit.ws.

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.

2 participants