Skip to content

Conversation

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Aug 14, 2025

This aligns ancestorOrigins exposure with referrer policy when using the iframe referrerpolicy attribute, so an embedder can prevent revealing its own origin to embedded documents. If an <iframe> uses referrerpolicy="no-referrer" or same-origin (and the parent and child are cross-origin), the parent’s origin and any same-origin ancestors are replaced with opaque origins (until reaching an ancestor that is cross-origin). Other policies continue to expose full origins. If there's no referrerpolicy attribute, the embedder document's referrer policy is used.

This approach keeps existing behavior by default (for web compat) while addressing privacy concerns with an opt-out.

The algorithm reuses the parent's existing list of ancestor origins, avoiding synchronous cross-process lookups and ensuring a stable snapshot even if ancestors mutate their referrerpolicy attributes later.

Fixes #1918. Closes #2480.

(See WHATWG Working Mode: Changes for more details.)


/iframe-embed-object.html ( diff )
/infrastructure.html ( diff )
/nav-history-apis.html ( diff )

@zcorpan
Copy link
Member Author

zcorpan commented Aug 14, 2025

This is the same as @annevk's #2480 but applied on top of the latest main. Rebasing involved a lot of unrelated conflicts, so instead I copied over the content in a new branch.

@annevk
Copy link
Member

annevk commented Aug 14, 2025

I think using the document's referrer is the wrong model. Because we don't care about the referrer of top-level example.com (how the user got to example.com, perhaps from search.example), we care about example.com's policy (or its iframe's policy) when embedding widget.example, when determining what information widget.example should have access to.

@zcorpan
Copy link
Member Author

zcorpan commented Aug 14, 2025

I created a doc to work out how to specify this with the new model: https://docs.google.com/document/d/1TDryRMiw7sVKBfrvnzEQk0PzGQk7_G7ShF4Uy68MvSU/edit?usp=sharing

Copy of the doc's current state

Example 1

  • example.org (no referrer policy)
    • Iframe referrerpolicy=”no-referrer”
      • Widget.example

Result: [“https://widget.example”, “null”]

Example 2

  • example.org (no referrer policy)
    • Iframe referrerpolicy=”no-referrer”
      • Widget.example and navigates to other.example

Result: ???

Algo

HTML:

  1. Let output be a new list of strings.
  2. Let currentDoc be the Location object's relevant Document.
  3. Let innerDoc be current.
  4. Let currentContainer be null.
  5. While currentDoc’'s container document is non-null:
    1. Set currentContainer to current’s node navigable’s container.
    2. Set currentDoc to current's container document.
    3. Let origin be the result of getting an origin if the referrer policy allows given currentContainer, currentDoc, and innerDoc.
    4. Append the serialization of current's origin to output.
  6. Return output.

Referrer Policy:

To get an origin if the referrer policy allows given an element or null container, a document doc, and a document innerDoc:

  1. Let elementReferrerPolicy be the empty string.
  2. If element is not null, then set elementReferrerPolicy to element’s referrerpolicy attribute’s state.
  3. Let docReferrerPolicy be doc’s referrer policy.
  4. Let referrerPolicy be elementReferrerPolicy.
  5. If referrerPolicy is the empty string, then set referrerPolicy to docReferrerPolicy.
  6. If referrerPolicy is “no-referrer”, then return “null”.
  7. If referrerPolicy is “same-origin” and doc’s origin is not same origin with innerDoc’s origin, then return “null”.
  8. If referrerPolicy is “strict-origin”, "strict-origin-when-cross-origin", “no-referrer-when-downgrade”, and innerDoc’s URL is not potentially trustworthy but doc’s URL is potentially trustworthy, then return “null”.
  9. Return the serialization of doc’s origin.

@zcorpan
Copy link
Member Author

zcorpan commented Sep 1, 2025

Doc updated. I've changed the algorithm to avoid exposing A's origin in the A->A->C case with the innermost iframe having referrerpolicy="no-referrer". Also added examples from w3c/webappsec-referrer-policy#77 (comment) and a polyfill to test with.

Copy of the doc's current state

Algorithm

HTML:

  1. Let output be a new list of strings.
  2. Let currentDoc be the Location object's relevant Document.
  3. Let innerDoc be currentDoc.
  4. Let currentContainer be null.
  5. Let prevOrigin and prevMasked be uninitialized.
  6. While currentDoc’s container document is non-null:
    1. Set currentContainer to currentDoc’s node navigable’s container.
    2. Set currentDoc to currentDoc's container document.
    3. Let « origin, masked » be the result of [=getting an ancestor origin if the referrer policy allows=] given currentContainer, currentDoc, and innerDoc.
    4. If prevMasked is true and origin is same origin with prevOrigin, then set masked to true.
    5. If masked is true, then append “null” to output.
    6. Otherwise, append the serialization of origin to output.
    7. Set prevOrigin to origin.
    8. Set prevMasked to masked.
  7. Return output.

Referrer Policy:

To get an ancestor origin if the referrer policy allows given an Element container, a Document doc, and a Document innerDoc:

  1. Let elementReferrerPolicy be the empty string.
  2. If container is an iframe element, then set elementReferrerPolicy to container’s referrerpolicy attribute’s state.
  3. Let docReferrerPolicy be doc’s policy container’s referrer policy.
  4. Let referrerPolicy be elementReferrerPolicy.
  5. If referrerPolicy is the empty string, then set referrerPolicy to docReferrerPolicy.
  6. Let masked be false.
  7. If referrerPolicy is “no-referrer”, then set masked to true.
  8. Otherwise, if referrerPolicy is “same-origin” and doc’s origin is not same origin with innerDoc’s origin, then set masked to true.
  9. Return « doc’s origin, masked ».

Note: Since mixed content checks prevent non-secure context documents in secure context documents, there’s no need to check secure context for “strict-origin”, "strict-origin-when-cross-origin", and “no-referrer-when-downgrade”.

Polyfill + demo

https://software.hixie.ch/utilities/js/live-dom-viewer/saved/14030

Examples

iframe referrerpolicy=”no-referrer”

  • Top-level browsing context, default referrer policy, origin: https://a.com
    • iframe referrerpolicy=”no-referrer”, origin: https://b.com

Child: ["null"]

Examples below are from w3c/webappsec-referrer-policy#77 (comment)

top -> sandboxed iframe -> 3rd party iframe (ad)

  • Top-level browsing context, default referrer policy, origin: https://a.com
    • Iframe sandbox, origin: opaque
      • Iframe, origin: https://b.com

Grandchild: ["null","https://a.com"]
Child: ["https://a.com"]

{1, a.com} default referrer policy -> loads {2, b.com} with noreferrer attribute on the iframe tag that's loading b.com -> loads {3, a.com}

  • Top-level browsing context, default referrer policy, origin: https://a.com
    • iframe referrerpolicy=”no-referrer”, origin: https://b.com
      • iframe, origin: https://a.com

Grandchild: ["https://b.com","null"]
Child: ["null"]

{1, a.com} default referrer policy -> loads {2, b.com} with noreferrer attribute on the iframe tag inside {2} -> loads {3, a.com}

  • Top-level browsing context, default referrer policy, origin: https://a.com
    • iframe, origin: https://b.com
      • Iframe referrerpolicy=”no-referrer”, origin: https://a.com

Grandchild: ["null","https://a.com"]
Child: ["https://a.com"]

{1, a.com} default referrer policy -> loads {2, a.com} with noreferrer attribute on the iframe tag inside {2} -> loads {3, c.com}

  • Top-level browsing context, default referrer policy, origin: https://a.com
    • iframe, origin: https://a.com
      • iframe referrerpolicy=”no-referrer”, origin: https://c.com

Grandchild: ["null","null"]
Child: ["https://a.com"]

{1, a.com} default referrer policy -> loads {2, b.com} with default referrer policy -> loads {3, b.com} with noreferrer attribute on the iframe tag inside {3} -> loads {4, a.com}

  • Top-level browsing context, default referrer policy, origin: https://a.com
    • iframe, origin: https://b.com
      • iframe, origin: https://b.com
        • iframe referrerpolicy=”no-referrer”, origin: https://a.com

Grandgrandchild: ["null","null","https://a.com"]
Grandchild: ["https://b.com","https://a.com"]
Child: ["https://a.com"]

@zcorpan
Copy link
Member Author

zcorpan commented Sep 2, 2025

After discussing with @farre we found out that reading the iframe's referrerpolicy attribute every time ancestorOrigins is read is not great. Instead, the value of the referrerpolicy attribute should be read when the document is created and stored on the document (or in its policy container).

@zcorpan zcorpan changed the title Redact ancestorOrigins using "the document's referrer" Redact ancestorOrigins using referrer policy Sep 3, 2025
@zcorpan
Copy link
Member Author

zcorpan commented Sep 5, 2025

reading the iframe's referrerpolicy attribute every time ancestorOrigins is read is not great.

This was not correct, the spec computes the ancestor origins list when the Location object is created, which happens when the Window object is created.

When a Location object is created, its ancestor origins list must be set to a DOMStringList object whose associated list is the list of strings that the following steps would produce:

https://html.spec.whatwg.org/#concept-location-ancestor-origins-list

Each Window object is associated with a unique instance of a Location object, allocated when the Window object is created.

https://html.spec.whatwg.org/#the-location-interface

So no need to store iframe's referrerpolicy. However, we want to minimize IPC and avoid inconsistency e.g. when an ancestor iframe's referrerpolicy attribute is mutated and a new child iframe is inserted (or is navigated). So the model we came up with now is to take the parent's ancestor origins list, mask some values as appropriate, and insert a new value.

Copy of the doc's current state

Algorithm

A Location object has an associated ancestor origin objects list. When a Location object is created, its ancestor origins list must be set to the list of origins that the following steps would produce:

  1. Let output be a new list of origins.
  2. Let innerDoc be the Location object's relevant Document.
  3. Let parentDoc be innerDoc’s container document.
  4. If parentDoc is non-null:
    1. Assert: parentDoc is fully active.
    2. Let parentLocation be parentDoc’s relevant global object’s Location object.
    3. Let ancestorOrigins be parentLocation’s ancestor origins list’s associated list.
    4. Let container be innerDoc’s node navigable’s container.
    5. Let referrerpolicyAttribute be the empty string.
    6. If container supports a referrer policy attribute (e.g., container is an iframe element), then set referrerpolicyAttribute to container’s referrer policy attribute’s state.
    7. Let docReferrerPolicy be parentDoc’s policy container’s referrer policy.
    8. Let referrerPolicy be referrerpolicyAttribute.
    9. If referrerPolicy is the empty string, then set referrerPolicy to docReferrerPolicy.
    10. Let masked be false.
    11. If referrerPolicy is “no-referrer”, then set masked to true.
    12. Otherwise, if referrerPolicy is “same-origin” and parentDoc’s origin is not same origin with innerDoc’s origin, then set masked to true.
    13. If masked is true, then append “null” to output.
    14. Otherwise, append parentDoc’s origin to output.
    15. For each ancestorOrigin in ancestorOrigins:
      1. If masked is true:
        1. If ancestorOrigin is same origin with parentDoc’s origin, then append a new opaque origin to output.
        2. Otherwise, append ancestorOrigin to output and set masked to false.
      2. Otherwise, append ancestorOrigin to output.
  5. Return output.

Note: Since mixed content checks prevent non-secure context documents in secure context documents, there’s no need to check secure context for “strict-origin”, "strict-origin-when-cross-origin", and “no-referrer-when-downgrade”.

A Location object has an associated ancestor origins list. When a Location object is created, its ancestor origins list must be set to a DOMStringList object whose associated list is the list of strings the following steps would produce:

  1. Let ancestorOrigins be this’s ancestor origin objects list.
  2. Let output be a new list of strings.
  3. For each origin in ancestorOrigins:
    1. Append the serialization of origin to output.
  4. Return output.

Polyfill + demo

https://software.hixie.ch/utilities/js/live-dom-viewer/saved/14047

Examples

iframe referrerpolicy=”no-referrer”

  • Top-level browsing context, default referrer policy, origin: https://a.com

Child: ["null"]

{1, a.com} default referrer policy -> loads {2, b.com} -> loads {3, a.com}

Grandchild: ["https://b.com","https://a.com"\]
Child: ["https://a.com"\]

Examples below are from w3c/webappsec-referrer-policy#77 (comment)

top -> sandboxed iframe -> 3rd party iframe (ad)

  • Top-level browsing context, default referrer policy, origin: https://a.com

Grandchild: ["null","https://a.com"\]
Child: ["https://a.com"\]

{1, a.com} default referrer policy -> loads {2, b.com} with noreferrer attribute on the iframe tag that's loading b.com -> loads {3, a.com}

Grandchild: ["https://b.com","null"\]
Child: ["null"]

{1, a.com} default referrer policy -> loads {2, b.com} with noreferrer attribute on the iframe tag inside {2} -> loads {3, a.com}

Grandchild: ["null","https://a.com"\]
Child: ["https://a.com"\]

{1, a.com} default referrer policy -> loads {2, a.com} with noreferrer attribute on the iframe tag inside {2} -> loads {3, c.com}

Grandchild: ["null","null"]
Child: ["https://a.com"\]

{1, a.com} default referrer policy -> loads {2, b.com} with default referrer policy -> loads {3, b.com} with noreferrer attribute on the iframe tag inside {3} -> loads {4, a.com}

Grandgrandchild: ["null","null","https://a.com"\]
Grandchild: ["https://b.com","https://a.com"\]
Child: ["https://a.com"\]

TODOs

@zcorpan
Copy link
Member Author

zcorpan commented Sep 8, 2025

I've updated this PR, the algorithm should be the same as in the doc.

theIDinside added a commit to theIDinside/firefox that referenced this pull request Sep 10, 2025
This implements this attribute on Location and also adheres to the
changes to the spec introduced in
whatwg/html#11560

Due to the tentative nature of the spec, and details
still being hashed out, this PR is subject to minor
changes.
theIDinside added a commit to theIDinside/firefox that referenced this pull request Sep 10, 2025
This follows the current revamping of the spec found at whatwg/html#11560 and will change with it, potentially.
@zcorpan zcorpan added the agenda+ To be discussed at a triage meeting label Sep 25, 2025
@zcorpan
Copy link
Member Author

zcorpan commented Oct 8, 2025

I wrote a summary of what this change does in the OP. (To be used in the commit message when squashing.)

@noamr
Copy link
Collaborator

noamr commented Oct 13, 2025

cc @domfarolino

@zcorpan zcorpan removed the agenda+ To be discussed at a triage meeting label Oct 23, 2025
@annevk
Copy link
Member

annevk commented Oct 23, 2025

Before I forget: we need to patch https://w3c.github.io/ServiceWorker/#client-ancestororigins at the same time.

This aligns `ancestorOrigins` exposure with referrer policy, so an embedder can prevent revealing its own origin to embedded documents. If an `<iframe>` uses `referrerpolicy="no-referrer"` or `same-origin` (and the parent and child are cross-origin), the parent’s origin and any same-origin ancestors are replaced with opaque origins (until reaching an ancestor that is cross-origin). Other policies continue to expose full origins. If there's no `referrerpolicy` attribute, the embedder document's referrer policy is used.

This approach keeps existing behavior by default (for web compat) while addressing privacy concerns with an opt-out.

The algorithm reuses the parent's existing list of ancestor origins, avoiding synchronous cross-process lookups and ensuring a stable snapshot even if ancestors mutate their `referrerpolicy` attributes later.

Fixes #1918. Closes #2480.
@zcorpan zcorpan force-pushed the zcorpan/redact-ancestororigins branch from 3c5e61f to 0004ccb Compare October 27, 2025 21:50
@zcorpan
Copy link
Member Author

zcorpan commented Oct 27, 2025

@annevk as far as I can tell, the call sites for Create Window Client use a Location object's ancestor origins list, which is still defined and compatible with this PR. But we should have tests for it.

@johannhof
Copy link
Member

What is the reason for using the referrer policy instead of a new attribute / header? Are we not concerned that this would lead to situations where one of the two features is required, making it impossible for sites to opt out of the other?

theIDinside added a commit to theIDinside/firefox that referenced this pull request Nov 7, 2025
This implements this attribute on Location and also adheres to the
changes to the spec introduced in
whatwg/html#11560

Due to the tentative nature of the spec, and details
still being hashed out, this PR is subject to minor
changes.
@zcorpan
Copy link
Member Author

zcorpan commented Nov 17, 2025

@theIDinside In https://html.spec.whatwg.org/multipage/browsing-the-web.html#populating-a-session-history-entry:determining-navigation-params-policy-container-2 responsePolicyContainer is passed (defined in step 9).

It seems as though each document always only gets default-values-only for it's referrer policy value in the policy container?

No, the Referrer-Policy response header can set it to something else, or the policy container can be cloned from another document (e.g. iframe srcdoc or just iframe with about:blank).

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

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

This looks good to me, but I do think we should wrap per our current rules before landing. This is also assuming we have test coverage.

theIDinside added a commit to theIDinside/firefox that referenced this pull request Nov 19, 2025
This implements this attribute on Location and also adheres to the
changes to the spec introduced in
whatwg/html#11560

Due to the tentative nature of the spec, and details
still being hashed out, this PR is subject to minor
changes.
theIDinside pushed a commit to theIDinside/firefox that referenced this pull request Nov 19, 2025
This follows the current revamping of the spec found at whatwg/html#11560 and will change with it, potentially.
@theIDinside
Copy link

@annevk I wrote some tests, I know you've written some tests before, but do you think these would do?

I guess the only test that is missing from my suite, is the one where a server responds with headers having been set to something like no-referrer, but it's unclear to me what effect that should have. If A->B->C and A gets served with headers with no-referrer in it's referrer policy, it's still the iframe attribute (for B) that determines first and foremost. I take it, A's response headers (via policy container) would be the deferred-to-value, if <iframe src=B> has no attribute? @zcorpan?

@annevk
Copy link
Member

annevk commented Nov 19, 2025

I think that A's Referrer-Policy header is indeed supposed to be the fallback, but how this works out in the specification and I'm not entirely sure. It almost seems like it's missing?

@theIDinside
Copy link

I think that A's Referrer-Policy header is indeed supposed to be the fallback, but how this works out in the specification and I'm not entirely sure. It almost seems like it's missing?

yeah, from my suite, it's the thing that's missing, I just needed some clarification from zcorpan on what that would be. by using this the tests should be able to incorporate headers while also having the tests look similar to a lot of other tests where I cargo culted the general structure from.

@domfarolino
Copy link
Member

but how this works out in the specification and I'm not entirely sure. It almost seems like it's missing?

I don't think anything is missing spec-wise, if that's what you mean. The referrerpolicy attribute directly informs the document state's referrer policy used for fetching, which could be the empty string if unset. If unset/empty string, then Fetch will override with the request's policy container, which will be the client's referrer policy as set by the response header. So that's how the fallback works.

@domfarolino
Copy link
Member

@annevk I wrote some tests, I know you've written some tests before, but do you think these would do?

Could you make a PR there, so we can review it? It looks like it's just a branch comparison right now, so I can't add any comments.

source Outdated

<li><p>If <var>referrerPolicy</var> is the empty string, then set <var>referrerPolicy</var> to
<var>parentDoc</var>'s <span data-x="concept-document-policy-container">policy container</span>'s
<span data-x="policy-container-referrer-policy">referrer policy</span>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

Do we have a WPT test where a parent document is served with a loose referrer policy, but later in life sets a tight one via <meta name=referrer content=no-referrer>, and we test that the iframe that's created after this DOES NOT see its parent's origin in the ancestor origins list?

We also want the inverse test where a document sets a looser referrer policy later in life, before an iframe is created; then we assert that the iframe can see the embedder's origin.

This should ensure all of the policy container stuff is wired up correctly in implementations.

Copy link

@theIDinside theIDinside Nov 21, 2025

Choose a reason for hiding this comment

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

My latest version of the test has these tests, but for the referrerPolicy on the <iframe> (and them not affecting the grandchild that is, not the direct descendant). I'll be sure to add 2 for your <meta> example here as well, as well as submit a PR to WPT with the test, for commenting purposes and have the .tentative one in the mozilla repo be the one ultimately upstreamed, if that works? or maybe @zcorpan can help me out here wrt this.

<li><p>If <var>container</var> is an <code>iframe</code> element, then set
<var>referrerPolicy</var> to <var>container</var>'s <code
data-x="attr-iframe-referrerpolicy">referrerpolicy</code> attribute's state's corresponding
keyword.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

Can I ask how this is implemented in other browsers? Maybe @theIDinside can weigh in. I presume a snapshot of the iframe's referrerpolicy attribute is taken at some point, or things can get racy, right? For example, what if an <iframe referrerpolicy=no-referrer src=slow.html> is created, but before its inner document is created, it loosens or removes its rp attribute? Would the inner doc see the "snapshot" of the rp attribute at creation time? I presume browsers with site isolation would actually implement it that way, instead of live-reaching-up into the embedder's process and querying the attribute.

Choose a reason for hiding this comment

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

Can I ask how this is implemented in other browsers? Maybe @theIDinside can weigh in. I presume a snapshot of the iframe's referrerpolicy attribute is taken at some point, or things can get racy, right? For example, what if an <iframe referrerpolicy=no-referrer src=slow.html> is created, but before its inner document is created, it loosens or removes its rp attribute? Would the inner doc see the "snapshot" of the rp attribute at creation time? I presume browsers with site isolation would actually implement it that way, instead of live-reaching-up into the embedder's process and querying the attribute.

This is actually exactly how I've implemented it in Gecko, some minor changes may happen going forward, but the gist will be the same - prepend the metadata at load/navigation. This is how allowfullscreen on <iframe> works too, so there's precedent for this as well, so user agents should not expect/require lazy evaluation of the attribute.

Copy link
Member

Choose a reason for hiding this comment

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

This is actually exactly how I've implemented it in Gecko

The snapshotting approach you mean, right? @zcorpan how do you feel about documenting this as a note below the step that pulls the attribute value from the iframe? Just documenting that implementations will probably implement this as a snapshot at iframe creation time to avoid races, and that the spec doesn't fully account for this kind of thing. We documented a few things like this during the navigation rewrite, since the spec doesn't fully account for all of the cross-process scheduling machinery that browsers usually have. I don't feel strongly.

Choose a reason for hiding this comment

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

The snapshotting approach you mean, right?

Right. But technically not at iframe creation though. The snapshot as it were, that I've went with is when the iframe gets navigated (similarly to how allowfullscreen works). Creation implies heavily that it'll only happen once. Otherwise we wouldn't be able to create an iframe, change meta, change the attribute on the iframe and then navigate the iframe and have that be seen in the child.

Copy link
Member Author

Choose a reason for hiding this comment

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

https://whatpr.org/html/11560/90c74b7...0307930/nav-history-apis.html#the-location-interface says

Each Window object is associated with a unique instance of a Location object, allocated when the Window object is created.

A Window is created in https://html.spec.whatwg.org/#initialise-the-document-object or https://html.spec.whatwg.org/#creating-a-new-browsing-context

Since the steps to create a Location object are sync, this shouldn't be racy.

Copy link
Member

Choose a reason for hiding this comment

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

There's no race between the Window and Location objects being created, but there is a race between the state of the referrerpolicy attribute at navigation time, and the Window/Location objects being created. There's no guarantee that the referrerpolicy attribute at navigation time will be in the same state by the time the navigation completes, https://html.spec.whatwg.org/#initialise-the-document-object runs, and the Window/Location objects are created and finally reach up to the referrerpolicy attribute.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed. Currently for the load of iframe src, the referrerpolicy attribute is snapshotted before calling "navigate". If that frame is later navigated by e.g. following a hyperlink, the iframe's referrerpolicy attribute is not used, as far as I can tell. For ancestorOrigins I think we want it to apply even if the iframe navigates itself. If we want the timing to be at the start of the navigation, we need some new plumbing to keep track of the iframe referrerpolicy value.

The sandbox and allow attributes are snapshotted in https://html.spec.whatwg.org/#initialise-the-document-object and https://html.spec.whatwg.org/#creating-a-new-browsing-context . In my mind it makes sense to snapshot the referrerpolicy here as well when it's for ancestorOrigins.

@theIDinside
Copy link

@zcorpan || @annevk - I had an interesting question during review; "Are there tests when iframe has data-url ancestors (if that is possible)?"

What would this imply for this spec and the results? We don't have any tests and I personally don't know yet, whether or not this scenario is possible or not.

@domfarolino
Copy link
Member

@theIDinside data: URL Documents can certainly have child iframes, so yes, those iframes can have data: URL ancestors. data: URL documents have opaque origins, but I don't think there's anything too special about this use case or how it interacts with this PR. Is there some result or case you're particularly concerned about or wondering about? We're just dealing with opaque origins...

@theIDinside
Copy link

@theIDinside data: URL Documents can certainly have child iframes, so yes, those iframes can have data: URL ancestors. data: URL documents have opaque origins, but I don't think there's anything too special about this use case or how it interacts with this PR. Is there some result or case you're particularly concerned about or wondering about? We're just dealing with opaque origins...

Ok, I was asked the question during review (for the gecko work) and I didn't really have a good answer for it so I figured maybe I could gain better clarity here.

@theIDinside
Copy link

Added a PR @domfarolino for the tests. I've also added additional testing for your example of tightening/loosening, so that it only affects the direct child when it's created.

@domfarolino
Copy link
Member

I spoke with some Chrome people about this change, and we're pretty worried about web compatibility. Has there been a compat analysis done? A lot of spam and fraud detection relies on iframes knowing the top frame's origin, so we're worried about breaking those and other use cases randomly by changing what all a Document's referrer policy influences.

@zcorpan
Copy link
Member Author

zcorpan commented Nov 26, 2025

I spoke with some Chrome people about this change, and we're pretty worried about web compatibility. Has there been a compat analysis done?

No, but I can take a look at httparchive.

A lot of spam and fraud detection relies on iframes knowing the top frame's origin, so we're worried about breaking those and other use cases randomly by changing what all a Document's referrer policy influences.

Interesting, can you say more? Are there known common libraries for this?

If we provide an opt-out (even if it's a new attribute), then spam and fraud detection scripts would have to blocklist "null" origins. Or maybe we should make it impossible for the top-level frame to hide its origin?

@SpaceGnome
Copy link

I spoke with some Chrome people about this change, and we're pretty worried about web compatibility. Has there been a compat analysis done?

No, but I can take a look at httparchive.

A lot of spam and fraud detection relies on iframes knowing the top frame's origin, so we're worried about breaking those and other use cases randomly by changing what all a Document's referrer policy influences.

Interesting, can you say more? Are there known common libraries for this?

If we provide an opt-out (even if it's a new attribute), then spam and fraud detection scripts would have to blocklist "null" origins. Or maybe we should make it impossible for the top-level frame to hide its origin?

+1 would prefer for it to impossible for the top-level frame to hide its origin.

This is a valuable signal for ad fraud detection and brand safety, and we'd prefer this change to not happen unless there are alternatives in place.

@jkarlin
Copy link

jkarlin commented Dec 1, 2025

| If we provide an opt-out (even if it's a new attribute), then spam and fraud detection scripts would have to blocklist "null" origins. Or maybe we should make it impossible for the top-level frame to hide its origin?

Yes, being able to opt out would be helpful. For ads I think it's tricky because they purchase the ad slot before loading on the page. It seems like the ads ecosystem will need to adapt and require that the referrer of the top frame (perhaps the entire tree, not sure) is available, and sellers will need to pass that availability information along to buyers at auction time, or face having unhappy buyers. It's kind of a not-the-browser's problem but I do think it's important to give the ecosystem plenty of time to adjust before making this breaking change.

@michaelkleber
Copy link

It seems to me that Dom's #11560 (comment) and Josh's #11560 (comment) are exactly the situation that @johannhof was worried about in #11560 (comment).

Josh's use case: an ad network only wants to pay to show an ad if that ad is appearing on the site they thought they were buying space on. Seems reasonable to me.

  • If ancestorOrigins reveals that an ad is actually rendering on malicious-site.example though it bid on ad space on respectable-news-site.example, then the ad network can say "oh this is fraud", can refuse to pay out, etc.
  • Therefore, malicious-site.example would sensibly use referrer policy to hide itself
  • Therefore, the ad network might very plausibly institute a new policy, "we refuse to show ads on sites that hide the top-level domain name".
  • Therefore, sites that are currently happily using Referrer-Policy: same-origin would suddenly suffer financially from doing so

The financial hit might come in the form of those sites seeming like fraud, or in the form of ad buyers simply refusing to buy ads there (if the ad industry adapts as Josh predicts and ad buyers can know what to expect ahead of time). But in either case, a privacy setting that sites already use today would acquire a new and perhaps undesirable cost.

@annevk
Copy link
Member

annevk commented Dec 2, 2025

Or maybe we should make it impossible for the top-level frame to hide its origin?

This would go counter to the goals I think. In that you should be able to embed a common widget without that widget learning about all the websites it's on.

Also, the point of using referrer policy is to align it with the information that was available to websites before ancestorOrigins was introduced. Perhaps ancestorOrigins has been around for so long now that it no longer holds, but it's still not in Firefox and libraries have to deal with that too.

@zcorpan
Copy link
Member Author

zcorpan commented Dec 2, 2025

Thanks for that additional context @michaelkleber, @jkarlin, @SpaceGnome!

Being able to embed a widget without it knowing where it is embedded, and being able to embed an ad without letting the embedder hide itself, seem to be conflicting requirements.

Therefore, sites that are currently happily using Referrer-Policy: same-origin would suddenly suffer financially from doing so

This seems a bit disruptive, if there are a lot of pages doing this.

Checking Chromium use counters:

These are all high, but account for any value. The ones impacted here are no-referrer and same-origin.

Checking 1% of httparchive, I see this distribution of values:

iframe referrerpolicy

referrer_policy_value count_pages
no-referrer-when-downgrade 3439
strict-origin-when-cross-origin 3161
origin 52
unsafe-url 23
no-referrer 18
strict-origin 4
strict-origin-when- 1
no-follow 1
no-referrer-when- 1
no-referrer-when-downgrad 1

Referrer-Policy header

referrer_policy_value count_pages
strict-origin-when-cross-origin 15468
no-referrer-when-downgrade 8566
same-origin 3682
no-referrer 1497
origin-when-cross-origin 967
strict-origin 941
origin 496
unsafe-url 374
  342
no-referrer, strict-origin-when-cross-origin 216
origin-when-cross-origin, strict-origin-when-cross-origin 207
no-referrer-when-downgrade, strict-origin-when-cross-origin 205
: no-referrer-when-downgrade, strict-origin 170
same-origin, strict-origin-when-cross-origin 17
no-referrer, same-origin 11
... ...

The total number of pages in the dataset is 23,517,510, so 1% of that is 235,175. This means about 2.2% of pages have Referrer-Policy: same-origin or Referrer-Policy: no-referrer. If we assume the numbers for meta is about the same, the impact is about 4.4% of pages.

But for iframe the numbers are more encouraging: same-origin didn't show up at all and no-referrer is 18 pages, or ~0.0077% of pages.

New proposal

If we drop the "fallback" to use the policy container referrer policy (which is set by Referrer-Policy header or a meta element), we don't change the behavior for ~99.99% of pages. But still allow pages to hide their own origin when embedding an iframe by using the referrerpolicy attribute.

Ad scripts that detect fraud will need to check for "null" in the ancestorOrigins list (either just the top value or maybe any value in the list). I think this should already be the case, because of Content-Security-Policy: sandbox (which can be used by top-level and makes the origin opaque).

@michaelkleber
Copy link

That "New proposal" sounds entirely reasonable to me.

Use of <iframe referrerpolicy="..."> would still allow pages to embed without revealing even the top-level domain to embedders, if they deliberately choose to do so. I certainly agree with Anne that that's a desirable capability. But it would only have that effect for pages that are trying to do it, rather than carrying along any page that intended to set policy for outbound links.

If I understand correctly, this would mean that the HTTP Referer: header for the fetch of an iframe src would follow the embedding page'sReferrer-Policy header (or meta element), while the ancestorOrigins value inside that iframe would follow the frame's referrerpolicy attribute. That's admittedly a bit incongruous. But in a sense this oddity stems from the longstanding conflation of the Referer header for subresource loads and for navigations.

@zcorpan
Copy link
Member Author

zcorpan commented Dec 2, 2025

Yes, exactly. Or, the fetch of the iframe src also looks at iframe referrerpolicy (here), but ancestorOrigins would only use the attribute.

@zcorpan
Copy link
Member Author

zcorpan commented Dec 2, 2025

That "New proposal" sounds entirely reasonable to me.

I've now made this change in this PR.

@theIDinside we can keep the tests that exercise Referrer-Policy and meta, but change the pass condition.

@zcorpan zcorpan changed the title Redact ancestorOrigins using referrer policy Redact ancestorOrigins using iframe referrerpolicy Dec 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

redact location.ancestorOrigins according to Referrer Policy

10 participants