Skip to content

fix(build): replace lodash's Function('return this')() with globalThis to bypass Firefox MV3 CSP#84

Closed
laurenthdl wants to merge 1 commit intoCrawlerCode:mainfrom
laurenthdl:fix/firefox-csp-globalthis
Closed

fix(build): replace lodash's Function('return this')() with globalThis to bypass Firefox MV3 CSP#84
laurenthdl wants to merge 1 commit intoCrawlerCode:mainfrom
laurenthdl:fix/firefox-csp-globalthis

Conversation

@laurenthdl
Copy link
Copy Markdown

Summary

Fixes #80: in Firefox the time tracker UI never appears on issue pages because the content script crashes at module init with EvalError: call to Function() blocked by CSP.

Root cause

The bundle includes lodash.debounce (transitively via usehooks-ts). At module init, lodash runs the historical globalThis fallback:

var u = typeof global == 'object' && global && global.Object === Object && global,
    d = typeof self   == 'object' && self   && self.Object   === Object && self,
    f = u || d || Function('return this')();

In a Firefox MV3 content script, the addon executes in an isolated world whose realm is distinct from the page's. As a consequence:

  • global is undefined → u is falsy
  • self.Object === Object is false (the content script's own Object is not the page's Object) → d is falsy
  • the fallback Function('return this')() is taken
  • and is blocked by the extension's default MV3 CSP (script-src 'self', no 'unsafe-eval')

This is reproducible on Firefox 127+ with v2.0.1 of the addon. Chrome is unaffected because Chrome's isolated world keeps Object identity with the page.

The exact location in the v2.0.1 build is content-scripts/content.js:1329 (col ~165050) and the lazy chunk chunks/timer-*.js.

End-user CSP relaxation is not a workaround: Firefox MV3 does not allow 'unsafe-eval' in content_security_policy.extension_pages, and the page-level CSP is irrelevant since this is the extension's own CSP that is enforced.

Fix

Add a small Vite plugin scoped to lodash modules that rewrites the expression to globalThis at build time:

const fixFirefoxCspGlobalThisFallback = () => ({
  name: "fix-firefox-csp-globalthis-fallback",
  enforce: "post" as const,
  transform(code: string, id: string) {
    if (!id.includes("lodash")) return null;
    if (!/Function\(\s*(['"`])return this\1\s*\)\s*\(\s*\)/.test(code)) return null;
    return code.replace(/Function\(\s*(['"`])return this\1\s*\)\s*\(\s*\)/g, "globalThis");
  },
});
  • No runtime change, no dependency change.
  • Scoped to lodash files so it cannot accidentally rewrite legitimate Function('return this')() expressions elsewhere.
  • globalThis is available since Firefox 65 / Chrome 71, well below the addon's strict_min_version: 127.0.

Verification

  • Built with pnpm run build:firefox. Bundle no longer contains Function(\return this`)(). The substituted line is now f = u || d || globalThis,in bothcontent-scripts/content.js` and the relevant lazy chunk.
  • Manually validated by patching the v2.0.1 release .xpi with the same substitution and loading it as a temporary addon in Firefox: the timer mounts and works as expected on issue pages.

Test plan

  • pnpm install
  • pnpm run build:firefox — succeeds
  • Built content-scripts/content.js and chunks/timer-*.js no longer contain Function('return this')()
  • Manual verification on a real Redmine instance with Firefox 150 — content script mounts, timer appears on issue page

🤖 Generated with Claude Code

…This` to bypass Firefox MV3 CSP

lodash uses `Function('return this')()` as a fallback when `global`/`self`
checks fail. In a Firefox MV3 content script, the addon runs in an isolated
world whose realm differs from the page's, so `self.Object === Object` is
`false` and the fallback path is taken. The expression is then blocked by
the extension's default MV3 CSP (`script-src 'self'`), throwing
`EvalError: call to Function() blocked by CSP` and preventing the content
script from mounting.

Add a small Vite plugin scoped to lodash modules that rewrites the
expression to `globalThis` at build time. No runtime/dependency change.

Fixes CrawlerCode#80
@CrawlerCode
Copy link
Copy Markdown
Owner

First of all, thank you very much for your contribution.

You’re right, and your solution would work, but I’ve actually already fixed the issue (see #83).

lodash.debounce isn’t really used in my project, but it’s still included in the bundle. usehooks-ts also doesn’t seem to be properly maintained anymore. That’s why I’ve decided to completely replace these legacy libraries with a better, well-maintained library (@mantine/hooks).

@CrawlerCode CrawlerCode added the duplicate This issue or pull request already exists label Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

duplicate This issue or pull request already exists

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants