Skip to content

Bug: Sec-Fetch-User is incorrectly added for non-user-activated requests, contrary to spec #228

@PurpleTape

Description

@PurpleTape

Hello!

I've identified a critical deviation from real browser behavior regarding how the Sec-Fetch-User header is generated. This discrepancy can signal to sophisticated bot detection systems that a request is automated.

The Problem: Incorrect Sec-Fetch-User Generation

The core of the issue is that impit unconditionally adds the Sec-Fetch-User: ?1 header to navigation-style requests. However, according to web standards, this header has a very specific meaning and should be omitted in most cases.

As stated in the MDN documentation, which reflects the Fetch standard:

The value will always be ?1. When a request is triggered by something other than a user activation, the spec requires browsers to omit the header completely.

— MDN Web Docs on Sec-Fetch-User

This means the header should only be present for navigations directly initiated by a user, such as clicking a link or typing a URL in the address bar. For all other requests, including loading subresources like iframes, images, scripts, or stylesheets, this header must be absent.

How to Reproduce

The issue can be easily observed by simulating a request for an iframe, which is not a direct user activation.

import { Impit } from 'impit';

const impit = new Impit({ browser: 'chrome' });

const response = await impit.fetch('https://httpbin.org/headers', {
    headers: {
        // Headers indicating a cross-site iframe navigation
        "Sec-Fetch-Dest": "iframe",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "cross-site",
        "Referer": "https://an-example-site.com/",
    },
});

const data = await response.json();
console.log('Sec-Fetch-User:', data.headers['Sec-Fetch-User']); 

Actual Result

The Sec-Fetch-User header is present with the value ?1.

{
  "headers": {
    "Sec-Fetch-Dest": "iframe",
    "Sec-Fetch-User": "?1",  // <-- This is incorrect and violates the spec
    "..."
  }
}

Expected Result

The Sec-Fetch-User header should be completely absent from the request.

Scope of the Issue: A Comprehensive Rule

The problem is not limited to iframe. It applies to any request that is not a user-activated, top-level navigation. The Sec-Fetch-Dest header provides the necessary context to make the correct decision.

Here is a clear rule based on browser behavior:

  • Sec-Fetch-User: ?1 SHOULD be sent only when Sec-Fetch-Dest is document, frame, or fencedframe, as these represent top-level document navigations that are typically user-initiated.

  • Sec-Fetch-User SHOULD BE OMITTED for all other destinations. This includes, but is not limited to:

    • Embedded content: iframe, embed, object
    • Subresources: script, style, image, font, video, audio, track
    • Workers & Worklets: worker, sharedworker, serviceworker, audioworklet
    • API calls: empty (for fetch())
    • Other: manifest, report, xslt, etc.

By default, impit adds the header for many of these, creating an easily detectable fingerprinting flaw.

Why This Is a Critical Issue

  1. Violation of Web Standards: The current behavior directly contradicts the Fetch specification, making the generated requests non-compliant.
  2. Bot Detection: Advanced WAFs and bot detection systems (Cloudflare, Akamai, etc.) analyze the logical consistency of Fetch Metadata. A request for an image (Sec-Fetch-Dest: image) with a Sec-Fetch-User: ?1 header is an impossible combination for a real browser and a strong, definitive signal of automation.
  3. Inability to Correct: This behavior cannot be fixed by the user. Setting "Sec-Fetch-User": undefined causes a crash, and setting it to another value is still incorrect. The library must handle the omission of the header correctly.

Proposed Solution

The library's internal header generation logic should be updated to respect the context provided by Sec-Fetch-Dest.

Suggested Logic:

Before sending a request, the logic for adding default headers should be as follows:

  1. Check the user-provided Sec-Fetch-Dest header (or the one inferred by impit).
  2. Only if Sec-Fetch-Dest is one of document, frame, or fencedframe, should the library add Sec-Fetch-User: ?1 (if it wasn't already provided by the user).
  3. For all other values of Sec-Fetch-Dest, the Sec-Fetch-User header must not be added.

This change would align impit's behavior with web standards, drastically improving its stealth and the authenticity of the browser fingerprints it generates.

Thank you for your time and for maintaining this valuable project.

Metadata

Metadata

Assignees

Labels

t-toolingIssues with this label are in the ownership of the tooling team.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions