Firefox Extension IDs: The Bad and the Ugly

Published: (April 9, 2026 at 04:26 AM EDT)
6 min read
Source: Dev.to

Source: Dev.to

Static Extension IDs

Both Chrome and Firefox let extension developers define a static extension ID in the manifest. This ID serves as a persistent identifier for the extension across installations and updates.

Chrome (and Chromium‑based browsers)

Extension‑ID handling works exactly as you’d expect:

  • You specify a public key in your manifest, which guarantees a static extension ID.
  • The browser uses this ID consistently.
  • All network requests from the extension include this ID in the Origin HTTP header.
  • Servers can identify which extension is making requests.
  • The ID remains the same across all installations of the extension.

Example – If your extension ID is cciilamhchpmbdnniabclekddabkifhb, every installation will use that ID, and every HTTP request will identify itself with the origin chrome-extension://cciilamhchpmbdnniabclekddabkifhb.

Firefox

Firefox also lets you specify a static extension ID in the manifest. However, at installation time Firefox generates a unique internal UUID for each installation. This UUID—not the static ID you specified—appears in the Origin header of HTTP requests.

On the surface this looks like a minor implementation detail, but in practice it creates significant problems.

The Bad: Breaking CSRF Protection

Cross‑Site Request Forgery (CSRF) protection is a fundamental security concern for any web application. The basic problem: how do you ensure that a request to your server came from your legitimate client application and not from a malicious site?

Traditional Web‑App Patterns

  • CSRF tokens embedded in forms
  • Origin HTTP‑header checks
  • SameSite cookie attributes

Browser extensions present a unique challenge. Extension code runs independently from web pages and isn’t subject to the same‑origin policy in the same way, so traditional CSRF mechanisms don’t work out‑of‑the‑box.

Origin Header: The Natural Solution

The Origin HTTP header was designed exactly for this purpose. When a browser makes a cross‑origin request, it includes an Origin header identifying where the request came from. For extensions, this header contains the extension ID.

Chrome Implementation

// Express.js example
app.post('/api/add', (req, res) => {
  const allowedOrigin = 'chrome-extension://cciilamhchpmbdnniabclekddabkifhb';

  if (req.headers.origin !== allowedOrigin) {
    return res.status(403).json({ error: 'Invalid origin' });
  }

  // Process the request…
});

This is secure, simple, and requires no user interaction. The extension can make “authenticated” requests to your server, and you can verify they’re coming from your legitimate extension, not from a malicious website or a rogue extension.

Firefox Complication

Because Firefox substitutes a per‑installation UUID for the static ID, the pattern above becomes impossible: you cannot allow‑list a specific origin because you don’t know what the UUID will be. Each user who installs your extension gets a different UUID.

The Workaround: Manual Configuration

The only reliable solution on Firefox today is to require users to manually configure a shared secret:

  1. User installs your extension.
  2. Server generates a secret token.
  3. User copies this token into the extension’s settings.
  4. Extension includes the token in all requests.
  5. Server validates the token instead of the Origin header.

Drawbacks

  • Extra setup steps discourage users.
  • High potential for user error.
  • Token management becomes the user’s problem.
  • You lose the ability to validate origin at the HTTP layer.

The Ugly: Privacy Implications

While breaking CSRF protection is bad for developers, Firefox’s internal‑UUID approach has even more troubling implications for user privacy.

A Built‑in Tracking Mechanism

The internal UUID is:

  • Unique per browser installation.
  • Persistent across all websites.
  • Completely unavoidable (cannot be disabled or cleared without reinstalling the browser).

Comparison

FeatureTracking CookiesFirefox Extension Internal UUID
Can be blocked by settings
Can be cleared by the user
Subject to SameSite policies
Visible to the user
Affected by privacy tools / private browsing
Unique per installation✅ (per cookie)✅ (per browser)

Why Did Firefox Do This?

I don’t have a definitive answer. Mozilla cites “sandboxing and security” reasons, but neither argument fully validates the use of an internal UUID in the Origin header.

Possible Reason 1: Security Isolation

Perhaps the intent was to provide better isolation between different extension installations. If each installation has a unique ID at the browser level, it could be theoretically harder for one malicious extension to impersonate another.

However, this benefit is questionable. Extension IDs are already validated by the browser; a malicious extension cannot fake another extension’s ID because the browser controls the Origin header.

Takeaways

  • Chrome: static extension ID → predictable Origin header → simple, reliable CSRF protection.
  • Firefox: per‑install UUID → unpredictable Origin header → broken CSRF patterns, forced manual token flow, and a hidden tracking vector.

If you need to support Firefox extensions today, you’ll have to:

  1. Accept the manual‑secret workflow or
  2. Implement a custom server‑side verification scheme that does not rely on the Origin header.

Until Firefox changes its behavior, developers must weigh the security‑vs‑privacy trade‑off and decide whether supporting Firefox extensions is worth the extra UX friction and privacy concerns.

Possible Reason 1: Privacy‑by‑Design

Firefox generates a random UUID for each extension installation and includes it in the Origin header. This makes it impossible for a server to know whether two requests came from the same browser installation, which helps prevent cross‑installation tracking.

Possible Reason 2: Migration from Legacy Extension System

Firefox underwent a major transition from legacy XUL extensions to WebExtensions. The internal UUID system might be a holdover from the legacy architecture that was never fully reconsidered.

Possible Reason 3: Accidental Consequence

It’s possible this wasn’t a deliberate design decision at all, but rather an accidental consequence of how Firefox’s extension system was architected.

Whatever the reason, the current behavior has serious flaws. You know the issue is serious when even Chrome offers a more privacy‑respecting solution.

UPDATE (2026‑02‑16)

Seems like the goal was to prevent extension fingerprinting.

The Developer Perspective

As someone building a free‑software project that prioritizes privacy and a local‑first architecture, Firefox’s behavior is frustrating.

For Users

  • Firefox users get a worse experience (manual configuration).
  • The browser marketed for privacy actually creates privacy issues.
  • No transparency about the internal UUID system.

For Developers

  • Can’t implement proper CSRF protection via the Origin header.
  • Must implement workarounds that harm UX.
  • Documentation becomes more complex.
  • Testing is harder (can’t easily simulate multiple Firefox installations).

What Should Firefox Do?

The solution is straightforward: use a static extension ID in the Origin HTTP header, just like Chrome does.

Disclaimer

While I’ve spent a significant amount of time researching and trying to find ways to resolve these issues, it’s possible I’ve completely missed something and there is a solution to either or both of the mentioned problems. In that case, please contact me on Mastodon: @asciimoo@chaos.social.

0 views
Back to Blog

Related posts

Read more »