Problem/Motivation

Google is introducing what it calls Federated Learning of Cohorts, which is a way to gather user data without cookies, regardless of whether a website is loading any Google-related trackers. This is enabled starting in Chrome 89, and only in select countries on a trial basis.

Although other major browser vendors are likely against this technology and will presumably not be implementing it, given Chrome’s market share this will become a concerning issue, because it largely remove users’ ability to easily opt out of being tracked—particularly true in the case of less-savvy users.

See a very informative post by Plausible.

Since no one can reasonably expect users to just stop using Chrome, it will be up to responsible developers to block FLoC at the source.

Steps to reproduce

Proposed resolution

Blocking FLoC is as easy as adding this header to the HTTP response:

Permissions-Policy: interest-cohort=()

Remaining tasks

User interface changes

None.

API changes

None.

Data model changes

None.

Release notes snippet

Introduce Permissions-Policy header to block Google’s Federated Learning of Cohorts.

Tasks Remaining

  • Create MR
  • Add tests
  • Create Change Record

Issue fork drupal-3209628

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

antiorario created an issue. See original summary.

xjm’s picture

Wim Leers’s picture

Issue tags: +privacy, +Privacy improvements

Also relevant reading: https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea.

As described in https://www.theverge.com/2021/4/16/22387492/google-floc-ad-tech-privacy-..., Brave and Vivaldi condemned it, while Edge, Firefox and Safari have "no plans to implement yet".

IMHO Drupal should disable it by default; at a minimum until it's been proven to not be a privacy risk.

e0ipso’s picture

Issue tags: +Values and Principles

I support this feature! Software freedom should be on the side of the user. Free open source software like Drupal has ethical motivations. Therefore I think it makes sense for this feature to be the default for all Drupal installs, and optionally turned off.

I think that an approach like the FLoC Blocker module would work in core.

The most important impact of this issue is that this becomes the default for all the future Drupal installs. Ethics included. 😄️

Wim Leers’s picture

For the record: our friends at WordPress are planning to block it too over at https://make.wordpress.org/core/2021/04/18/proposal-treat-floc-as-a-secu... — it's only a proposal, but the comments on this proposal are overwhelmingly in favor of blocking.

rachel_norfolk’s picture

Whilst we absolutely should work towards a core module that handles lots of privacy features, Wim has it right here - implement this by default until we incorporate it into something bigger.

Seems like a quick win to publicly declare our intentions.

Simon Georges’s picture

It seems the header will not be enough in some cases: https://seirdy.one/2021/04/16/permissions-policy-floc-misinfo.html.

mtift’s picture

I support this issue and agree that it makes sense in Drupal core.

phenaproxima’s picture

I, too, support blocking FLoC out of the box, for the utterly basic torches-and-pitchforks reason that I despise targeted advertising (actually all advertising, but that's a different discussion) with every fiber of my being and would like to do everything possible to harm, hamper, and stymie it. I'm a simple man, but +1 for this.

antiorario’s picture

I’m very happy this issue has gained some visibility and support. That said, the post Simon Georges is linking to in #7 is hitting some of my initial doubts on the head, and particularly:

I don’t think it’s right to place a burden or blame on webmasters when the burden and blame should rightfully be directed at those responsible for rolling this antifeature out in Chromium. We shouldn’t expect webmasters to add a tag or header every time Google advances the war against its own users.

I agree with most of that, but as a developer I feel like I’m swimming against an unstoppable tide of small (and less small) acts of prevarication coming from Google, and I personally have no way to make Chromium developers change their ways—or grow an ethical conscience. So I act at the level I have the most control over, and hope others will follow.

(The part I don’t agree with is the framing of this and other similar situations as Google going to war against its users, since this is more like hunting for prey—but I digress.)

If you’re concerned about Google breaking the spec and opting you in even after you’ve not done so yourself, what reason do you have to believe that they’ll stop there? There’s nothing preventing Google from ignoring your Permissions-Policy header, either.

Sharply aware of that. However:

Here’s how not to opt-in to Google’s FLoC:

  • Don’t load untrusted third-party content that might get classified as an ad (only applies during the origin trial)
  • Don’t call document.interestCohort(), and don’t load third-party scripts that might call it either.

With tools like Google Tag Manager at the disposal of any marketing department, this is a lost battle. And I’ll leave it at that.

But I will submit my code later today.

xmacinfo’s picture

In addition to blocking Google’s FLoC directly in core we should also make sure that Drupal.org (and sub-sites) implements it's own blocking mechanism now.

rachel_norfolk’s picture

In addition to blocking Google’s FLoC directly in core we should also make sure that Drupal.org (and sub-sites) implements it's own blocking mechanism now.

...noted

longwave’s picture

@xmacinfo I raised #3209738: Add Permissions-Policy header to block Google FLoC on drupal.org to deal with this on drupal.org itself.

xmacinfo’s picture

Contrib module for those who want to implements the FLoC shield now:

https://www.drupal.org/project/flocblock

rachel_norfolk’s picture

So, if we have some code in core, we need to ensure that it also checks that this header has not also been added by other means, or does that matter?

beltofte’s picture

So, if we have some code in core, we need to ensure that it also checks that this header has not also been added by other means, or does that matter?

Permissions Policy is a web platform API which gives a website the ability to allow or block the use of browser features in its own frame or in iframes that it embeds. It operates on the principle that top-level documents should generally have access to the web's powerful features (often at the discretion of the user, who needs to grant permission), but that embedded content should not have such access automatically. A document which embeds another document should be able to declare which features it trusts that embedded content to use.

The Permission-Policy header has other purposes, so we can't just overwrite it. If it exists will we have to add the value "interest-cohort=()" to the existing list of header values. Find more info about the header in w3c documentation.

rootwork’s picture

100% in favor of this. And while I read with interest the post linked from #7 and share most of its worries, especially:

Given that non-standard header usage has failed in the client-side fight against surveillance capitalism before, I’m not too optimistic about trying this again from the server side.

I also think this section is important:

It’s ideal for situations in which authors aren’t in control of what content is being loaded.

While Drupal developers could choose to use this header or not, end-user Drupal site admins/authors/clients may not have the power/interest/familiarity to take care of this themselves. And as noted by @antiorario in #10, there's the easily imagined scenario with a marketing department and Google Tag Manager.

With WordPress (noted above) and PHP.net already on board, I strongly support Drupal taking this step as aligning with our values and principles.

antiorario’s picture

Status: Active » Needs review

I made a commit to the issue fork: https://git.drupalcode.org/issue/drupal-3209628/-/commit/0ae3abe402d1690....

I considered what rachel_norfolk and beltofte said in #15 and #16, and decided against checking for existing Permissions-Policy headers, since this gets called so early in the game that we’re most likely not overwriting anything else. However, if the consensus is to still add that check, I can do that.

Perhaps something like this (although I might be overthinking it):

  public function onKernelResponse(ResponseEvent $event) {
    if (!$event->isMasterRequest()) {
      return;
    }

    if ($headers = $event->getResponse()->headers->get('Permissions-Policy')) {
      $headers = explode(',', $headers);
      array_walk($headers, function (&$header) {
        $header = trim($header);
      });

      $headers = array_diff_key($headers, preg_grep("/^interest-cohort/", $headers));
    }

    $headers[] = 'interest-cohort=()';
    $event->getResponse()->headers->set('Permissions-Policy', implode(', ', $headers));
  }
effulgentsia’s picture

I haven't read the spec and some of the other links in this issue yet, but I'm wondering if it's enough to only set the header from PHP. That would mean that it isn't set for requests to static files: whether that's images/css/js, or whether that's public files uploaded into a file field / media entity.

If Google honors the header from an HTML response and applies it to images/css/js loaded by that HTML as well, that would cover most static files, but would still leave public files. We could potentially add the header in the .htaccess file for the public files directory to address that if needed. It would be good to not have to add it to the docroot's .htaccess, but that's also a last resort option if the other places aren't sufficient.

beltofte’s picture

Status: Needs review » Active

I considered what rachel_norfolk and beltofte said in #15 and #16, and decided against checking for existing Permissions-Policy headers, since this gets called so early in the game that we’re most likely not overwriting anything else.

@antiorario Your solution makes sense when thinking more about it. If a large organization wants to use this header to block or restrict some of the supported browser features, then would they probably do it in their reverse proxy or webserver and not on application level. This should work fine with the implementation in your fork.

Wim Leers’s picture

For the record: our friends at WordPress are planning to block it too over at https://make.wordpress.org/core/2021/04/18/proposal-treat-floc-as-a-secu... — it's only a proposal, but the comments on this proposal are overwhelmingly in favor of blocking.

For the record: our friends at Joomla are likely going to block it by default as well: https://github.com/joomla/joomla-cms/pull/33212.

beltofte’s picture

For the record: our friends at Joomla are likely going to block it by default as well: https://github.com/joomla/joomla-cms/pull/33212.

One more for the record :-) The Umbraco team is currently discussing doing the same https://github.com/umbraco/Umbraco-CMS/issues/10147.

DamienMcKenna’s picture

Status: Active » Needs review

Should I open a separate issue to backport this to D7?

xmacinfo’s picture

> Should I open a separate issue to backport this to D7?

Yes, please do. Drupal 7 does not have a contrib module to block Google’s FLoC, so dealing with in core will be welcome.

beltofte’s picture

Status: Needs review » Active

Yes, please do. Drupal 7 does not have a contrib module to block Google’s FLoC, so dealing with in core will be welcome.

There is a D7 version on this project https://www.drupal.org/project/flocblock :-)

antiorario’s picture

I wouldn’t leave D7 behind 🙂

(But I’m also not sure what I did for D7 is the best solution, with hook_init() and all.)

DamienMcKenna’s picture

Cellar Door’s picture

I'll chime in here and say I think this is a great addition to core and one that as a community allows us to highlight that privacy is important to our work. To Rachel's point, eventually this should be taken into larger work around privacy and ensuring that site builders and owners are aware of the privacy implications of the various modules and capabilities of the site. Documentation around this change may be worthwhile as well, so eventually should a site owner want to reverse it they know how.

I'll give my +1 for the solution and the code in the branch by @antiorario.

steinmb’s picture

I support blocking it out of the box, both D7 and 8/9/10.

phenaproxima’s picture

Status: Active » Needs work
Issue tags: +Needs tests

This code looks very straightforward to me, though I requested one small nitpicky change.

More important is that we'll need test coverage, so marking this as "needs work" for that. I would suggest following the lead of \Drupal\Tests\system\Functional\System\ResponseGeneratorTest. Create a new test in the same namespace which does similar stuff, but checks for the appropriate Permissions-Policy header. (You can probably just copy ResponseGeneratorTest wholesale and tweak it appropriately. It might be a good idea to eventually merge this new test with ResponseGeneratorTest, maybe even before this is committed, but the easiest thing to do for now is to have them be separate tests.)

If we end up doing more complex logic to merge the interest-cohort directive with an existing Permissions-Policy header, we'll probably need to add another test as well -- most likely a unit or kernel test -- to prove that it behaves the way we expect it to. But right now, this code is very simple, so I think a simple test in the mold of ResponseGeneratorTest is appropriate.

rachel_norfolk’s picture

Hmm. Whilst I absolutely think we need this in core and we need it to be the default behaviour, I also think that a site owner should be able to disable it, even if we don't necessarily provide a UI to do so right now (until we have a comprehensive privacy section in Drupal core?).

Could we check for a Drupal setting, like $settings['interest_cohort_blocker_subscriber'] = FALSE; perhaps? If it is TRUE OR it is not defined, then assume that we can bloc FLoC but, if it is false, then don't block?

Think of the "I absolutely support the use of FLoC" site owner. We may not agree with their approach but we should cater for it, even minimally.

longwave’s picture

Re #32 this is very similar to the issue that has stalled #3104566: Implement Server Timing performance metrics where there is the opinion that some site owners might want to turn this feature off, but there is no consensus on the best way of allowing them to do so. Whatever we decide here might also help move that issue along.

Dries’s picture

I'd love to see this added to core and enabled by default. We should provide an option/mechanism to disable it though.

I already use a Permissions-Policy header on my personal blog, for example.

rachel_norfolk’s picture

Okay, if we have something like

  public function onKernelResponse(ResponseEvent $event) {
    if (!$event->isMasterRequest()) {
      return;
    }

    if (Settings::get('interest_cohort_blocker_subscriber', TRUE)) {
      return;
    }

    $event->getResponse()->headers->set('Permissions-Policy', 'interest-cohort=()');
  }

to add in a check, then

  1. Add an entry (with explanation) to default.settings.php
  2. Add a change record (as we are making a new setting)
  3. Add something in the Drupal User Guide to explain usage

are we heading in the right direction?

rachel_norfolk’s picture

Ooooh - whoops - that's what you get for writing code before breakfast...

Should be

if (!Settings::get('interest_cohort_blocker_subscriber', TRUE)) {
      return;
}

because we only to progress to setting the header if there is no setting or a setting is TRUE...

(I'm off to mess with spreadsheets and chat with community on Slack instead - clearly, that's where my skills lie these days 🤣)

phenaproxima’s picture

Add an entry (with explanation) to default.settings.php
Add a change record (as we are making a new setting)
Add something in the Drupal User Guide to explain usage

Yup, that seems correct to me! Plus test coverage, of course. :)

longwave’s picture

So these are the options that were proposed in the Server-Timings issue:

  1. Add it to development.services.yml
  2. Enable it be default, for example by adding it to core.services.yml
  3. Create a new module for it
  4. Toggle it via service parameter
  5. Toggle it via configuration option

1. doesn't apply here because we want this in more than just development sites.

2. is what the current patch implements; this enables it by default but makes it difficult for most site owners to remove again.

3. would enable it by default on new sites (e.g. if we added it to the Standard profile), and makes the feature both quite visible to site owners and easy to disable again if they don't want it (they can just uninstall the module). This is equivalent to adding https://www.drupal.org/project/flocblock to core.

4. is similar to 2. but easier to disable (they can add a line to services.local.yml)

5. is what is proposed in #35, there are multiple sub-options here; we can either do it in settings.php (less visible to site owners) or as a checkbox somewhere in an admin form with associated config storage (more visible, but where would it go?)

phenaproxima’s picture

IMHO, the easiest way to get this implemented quickly, while ensuring there is an escape hatch for savvy site operators, is a setting. Doing it in config means the MR will be bigger and potentially have an update path (to create the new config option). Adding anything in the UI means we'll probably have to undergo UX review. All of these things take time.

If we add it as a setting, then we get the best of all worlds: this gets done as soon as possible, but technical people can change the behavior. Dunno about you, but that sounds good to me!

rachel_norfolk’s picture

Issue summary: View changes

Okay, added the code above and an entry in default.settings.php

Also adding some tasks to the Issue Summary...

trebormc’s picture

I am activating right now https://www.drupal.org/project/flocblock in my websites. I think it is the easiest/fastest way right now.

I think it would be better to include this module in the core and let the installation profile be in charge of activating it by default. Users can always disable the module. Similar to what happens with other core modules like RDF.

xmacinfo’s picture

Status: Needs work » Needs review

I like the setting approach.

Marked as needs review. But this won't make it a candidate for RTBC until tests are cooked in.

phenaproxima’s picture

Status: Needs review » Needs work

To keep the status accurate, this should probably be "needs work" until tests are completed. :)

rachel_norfolk’s picture

Just adding same changes to the scaffolding default.settings.php

rachel_norfolk’s picture

Just to address @trebormc's comment in #41 for a moment, the reason to make this a self-contained event subscriber in core, rather than an extra module, even an extra core module, is to ensure that we can easily enable it for currently deployed sites. To try and add a new module in such a fashion would require new/changed configuration etc etc.

It also means that, should FLoC no longer be a thing Google choose to reply long term, deprecating this will be easier, too. We could simply remove it (with all due deprecation notices etc etc) without cause yet further config alternations etc.

trebormc’s picture

Thanks @rachel_norfolk , that makes sense.
It's true that it makes sense to be as simple and transparent to the common user as possible. And with this we avoid modifying configurations of currently active websites.
And it is true that if google abandons FLoC deprecar this is much more complex if it is a module.
I hadn't thought about it!!

neclimdul’s picture

Additional point against the core module, there's a lot of cost to modules so for a single subscriber built directly into core there just doesn't seem to be a reason to take on all that cost.

Bike shed but the setting name might be better as something descriptive instead of tied to the description. Something like 'block_interest_cohort'. Makes the behavior changed by the setting a little clearer and self documenting(IMHO).

effulgentsia’s picture

IMHO, the easiest way to get this implemented quickly, while ensuring there is an escape hatch for savvy site operators, is a setting. Doing it in config means the MR will be bigger and potentially have an update path (to create the new config option). Adding anything in the UI means we'll probably have to undergo UX review. All of these things take time.

Normally, I would say that something like this should be a config, not a setting. The vast majority of the time, that would require a UI as well, but occasionally we do allow new config to go in and defer the UI to a followup. Config (whether it has a UI or not) can be controlled via settings.php via the $config variable that's in that file, as well as via whatever config deployment process that the site uses.

As a general rule, $settings should only be for things that can't or shouldn't be in config (such as database connection info, or where your config sync directory is) or for environment-specific technical settings (like information about your reverse proxy if you're using one).

However, @phenaproxima is right that adding a new config object would require a post_update function, and we try to avoid those for patch releases. So from the perspective of backporting this to 8.9/9.0/9.1, adding a new key to $settings would be more expedient.

To balance the trade-offs, I think my preferred option at the moment would be to do it as a key in $settings for now, then have a follow-up for Drupal 9.3 to move it to a $config object and deprecate the $settings key, and then remove the $settings key in Drupal 10.0.

I pinged the other committers to get their opinion on this as well.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

longwave’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests

Renamed the setting as in #47 and added a test.

Two questions:

1. Should we only set the header if it's not already set?

    $event->getResponse()->headers->set('Permissions-Policy', 'interest-cohort=()');

If we pass FALSE as the third argument to set() it will not override the header if something has previously set it.

2. It seems unlikely that the setting will be changed regularly. Instead of checking the setting in onKernelResponse() should we move it to getSubscribedEvents() so it is only checked when the event subscribers are built, and then the event subscriber won't run at all once it has been set to FALSE?

phenaproxima’s picture

Status: Needs review » Needs work

Did a quick review of the merge request; a few small points!

phenaproxima’s picture

Should we only set the header if it's not already set?

I'm not sure. The problem here is that any contrib or custom code which innocently adds a Permissions-Policy header before our event subscriber runs could inadvertently allow FLoC, which may not be desired behavior.

It probably makes sense for us to set the header if it doesn't already exist, or append to an existing header if it does. My cursory research suggests that Permissions-Policy directives are separated by a semicolon.

It seems unlikely that the setting will be changed regularly. Instead of checking the setting in onKernelResponse() should we move it to getSubscribedEvents() so it is only checked when the event subscribers are built, and then the event subscriber won't run at all once it has been set to FALSE?

That seems a little more complicated than it needs to be, and I'm not aware of any core precedent for such a pattern. AFAIK, the current settings are stored in memory for the duration of the request, so I imagine it'd be pretty trivial to check them at runtime. Unless there's some other potential benefit that I'm not seeing?

longwave’s picture

Status: Needs work » Needs review

Still not sure what to do about the case where the header is already set. I checked contrib and flocblock is the only module that sets the header so far, so perhaps we can just do nothing or log a warning if the header exists.

I don't know if splitting on semicolon is enough, the spec for parsing a dictionary in an HTTP header seems non trivial if I follow all the links in https://www.w3.org/TR/permissions-policy-1/#algo-process-response-policy down to https://tools.ietf.org/html/rfc8941

Unless there's some other potential benefit that I'm not seeing?

AFAIK event subscribers are collected once when the container is built. Therefore if this is implemented when the setting is disabled the class will never even be loaded when the onKernelRequest event is fired, as it was already excluded at container build time. This is perhaps a micro-optimization that we don't need, though.

longwave’s picture

https://github.com/gapple/structured-fields is a PHP library for handling HTTP structured fields to the RFC 8941 spec but that seems a bit much to add for what is likely to be an edge case.

phenaproxima’s picture

I don't know if splitting on semicolon is enough

I'm actually not even suggesting we parse the header at all; just append to it if it's already set. Something like this (pseudocode):

$header = rtrim($response->getHeader('Permissions-Policy'), ';');
if ($header) {
  $header .= '; ';
}
$header .= 'interest-cohort=()';

So basically, just "append to an existing header, if there is one". No parsing involved!

longwave’s picture

What if interest-cohort already appears in the header?

rachel_norfolk’s picture

When does this event subscriber run, in the course of a response? Is there actually any chance at all that there could be anything there already? Do *any* contrib modules get hold of the response before this core event subscriber?

phenaproxima’s picture

What if interest-cohort already appears in the header?

Good question. I think we could probably just add the directive if it's not already there. Something like:

if (strpos($header, 'interest-cohort') === false) {
  $header .= 'interest-cohort=()';
}
When does this event subscriber run, in the course of a response?

Not sure. I think a bunch of event subscribers will act upon the response (including ones from contrib). Event subscribers can be prioritized explicitly, but if several subscribers have the same priority, I don't know how or if they are sorted.

Is there actually any chance at all that there could be anything there already?

There is definitely a chance. It would be fairly trivial for code to have a higher-priority event subscriber that sets a Permissions-Policy header. I do think it's an edge case, although I'm have no evidence to support that assumption.

longwave’s picture

I've had a look through existing code and wonder if we are over engineering this. Perhaps this should just be added to FinishResponseSubscriber::onRespond() which already adds a number of headers:

    // Set the X-UA-Compatible HTTP header to force IE to use the most recent
    // rendering engine.
    $response->headers->set('X-UA-Compatible', 'IE=edge', FALSE);

    // Set the Content-language header.
    $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());

    // Prevent browsers from sniffing a response and picking a MIME type
    // different from the declared content-type, since that can lead to
    // XSS and other vulnerabilities.
    // https://www.owasp.org/index.php/List_of_useful_HTTP_headers
    $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
    $response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE);

This event subscriber is also responsible for adding the various cacheability headers including the ones used for debugging.

Note here that in a number of cases we use FALSE as the third argument to avoid overwriting any headers that might have already been set.

effulgentsia’s picture

Perhaps this should just be added to FinishResponseSubscriber::onRespond() which already adds a number of headers

+1

Note here that in a number of cases we use FALSE as the third argument to avoid overwriting any headers that might have already been set

However, these examples are for headers that only have a single value. Because Permissions-Policy can have a list of features, I think checking if the header is already set but without the interest-cohort feature included, and in that case appending that feature, is a good idea.

Maeglin’s picture

Another factor that should be noted with the "Is the header already set?" question is web server config, and when headers are set that way compared to PHP setting them. In my Apache config, I have a Permissions-Policy header set to block hosted sites from using camera or microphone APIs (not hosting any web-based conferencing systems, so that makes sense), and I add interest-cohort to the list for sites that aren't really meant for public eyes, since it wouldn't make sense to include those in an advertising interest group.

Incidentally, policies stated in a Permissions-Policy header are separated by commas, not semicolons. Semicolons were used with the older Feature-Policy header.

longwave’s picture

Addressed #59/#60 by merging the event subscriber into FinishResponseSubscriber. Also added extra code and test cases to cover #60.2.

@Maeglin we cannot know what the web server config is, as the response is generated inside Drupal and then the web server is free to further modify that response. The underlying problem here is similar to #2854817: Duplicate X-Content-Type-Options headers both with the value nosniff. I can only suggest that if you are already adding Permissions-Policy at the web server level then you will need to disable the additional header in settings.php via the new flag.

Also thanks for the note about commas vs semicolons, I checked the spec and this is correct - the most recent code uses a comma to append a new policy if an interest-cohort is not already present.

rachel_norfolk’s picture

Issue summary: View changes
Status: Needs review » Needs work

Updating tasks needed in the Issue Summary.

Can someone confirm we do indeed need a change record here?

Going to have a look at the User Guide now…

rachel_norfolk’s picture

Issue summary: View changes
Status: Needs work » Needs review

Removing requirement to add to User Guide, having spoken with @Jhodgdon.

Back to Needs Review and the first review needed is whether we need a change record...

effulgentsia’s picture

I think we do need a change record. One, to let people know about the new setting in settings.php. Two, this is a potential BC break for the rare case where someone is conditionally setting the Permissions-Policy header (whether for the interest-cohort feature or for some other feature, like geolocation) in their .htaccess file or in a reverse proxy, if the header doesn't already exist. In the case of .htaccess that can be easily fixed by using the merge action instead of a conditional set. In the case of reverse proxies, it would need to be done using whatever syntax is available by that software.

rachel_norfolk’s picture

Here's a start on a change record. Trying to keep it as simple as possible - it's on by default and here's how to switch it off.

https://www.drupal.org/node/3213197

rootwork’s picture

Made some edits:

  • The second paragraph felt a little confusing the way it was structured (if you do want FLoC, add a line to your settings and set it to false) so I tried something different.
  • In the first paragraph I added a link to Wikipedia's entry on FLoC.

Other thoughts:

  • Assuming the Wikipedia link is wanted, do we want to avoid trying to characterize FLoC in the change record itself? Or leave it as "...a way to gather user data without cookies" without adding the part about Google?
  • Is it worth noting that other related open-source projects (WordPress, Joomla, Umbraco, PHP.net) have done the same thing?
  • Should we link to the module for D7, or is that outside the scope of a CR?
xmacinfo’s picture

Is it worth noting that other related open-source projects (WordPress, Joomla, Umbraco, PHP.net) have done the same thing?

I think that we should mention this in Tweets, Blog posts, Newsletter. Dries could write something. 🙂

No need to mention in the CR that other projects did the same thing.

rachel_norfolk’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Thanks for those change record updates, @rootwork, they help a lot.

That is all the remaining tasks completed. I'm going to set to RTBC and see what happens!

AaronMcHale’s picture

This is a positive addition to Core.

Perhaps the momentum here could also be used to consider other additions relating to privacy and security, for instance adding support to Core for configuring Content Security Policy.

Content Security Policy contrib module

larowlan’s picture

Review the MR and it looks good to me. Leaving to @effulgentsia to see this home, as he's been involved for some time

DamienMcKenna’s picture

Might there be any chance of this being added to 9.2.0, or is it too late for that?

webchick’s picture

Technically it missed the 9.2 alpha deadline, but due to strategic importance, there seems to be general concurrence that if it makes it in before beta, we can still get it into 9.2. 🙏

effulgentsia’s picture

Version: 9.3.x-dev » 9.2.x-dev
Issue tags: -Needs framework manager review, -Needs security review

The MR looks great. I'm going to commit this to 9.3.x and 9.2.x. In the default.settings.php file, I'm going to change the docs to link to https://en.wikipedia.org/wiki/Federated_Learning_of_Cohorts instead of https://plausible.io/blog/google-floc to match the change record. I can do that on-commit, no need to update the MR.

Adding issue credit to folks who reviewed the patch and/or CR. I'm not adding credit to people who only commented on the idea to do it, though I do appreciate everyone's input on that as well, thank you!

  • effulgentsia committed 573e752 on 9.3.x
    Issue #3209628 by longwave, rachel_norfolk, antiorario, phenaproxima,...

  • effulgentsia committed a27e41b on 9.2.x
    Issue #3209628 by longwave, rachel_norfolk, antiorario, phenaproxima,...
effulgentsia’s picture

Assigned: antiorario » Unassigned
Status: Reviewed & tested by the community » Fixed
Issue tags: +9.2.0 release notes, +9.2.0 release highlights

Thanks all!

gapple’s picture

I'm a little late on awareness of this issue, but it is... relevant to my interests... (Refactoring and updating the Feature Policy module to a Permissions Policy module is on my list of things [and was, but is no longer held up by needing to write the Structured Fields Values php library first]).

The name and documentation for $response->headers->set()'s third parameter is misleading, as it toggles between replacing an existing header or appending a new header of the same name (which raises some questions about whether it's being appropriately used on the existing calls within FinishResponseSubscriber...), so the has+set that was implemented is the right way to go.

I think appending to an existing header, is maybe not the right thing to do.
- If someone has a custom subscriber already setting a Permissions-Policy header, I think they're likely aware of FLOC and able to update their policy, and core shouldn't be making the intervention.
- I think more importantly though non-core service definitions are loaded after core's, and AFAIK that will cause module event subscribers with the same priority to execute later. As a result, core will (1) most likely never execute the append line of code, and (2) always have its header overwritten by any other implementation anyways (unless the custom subscriber was given a higher priority to execute earlier so that core could append to it, which would be an odd choice over just setting the desired value directly).
- (If subscriber order of equal priority was not deterministic, that would probably be a bigger issue as it would be inconsistent if core would append to a value set elsewhere or not).

---

Given the current concerns about FLOC, default configuration for the Permissions Policy module will include interest-cohort=() by default.

---

A tangent on @AaronMcHale's comment about Content Security Policy in core - I'm of mixed opinion given there are some gotchas that possibly prevent core from implementing anything other than a policy so permissive as to be meaningless without frustrating users who don't have some knowledge of CSP (and users with knowledge would be capable of installing and configuring a contrib module which could have a more restrictive default configuration than core would be able to).

My efforts for now are: improving core to no longer require policy exceptions (see the upstream issues in the CSP issue queue); supporting other contrib modules in integrating with CSP (and also not requiring unsafe policy exceptions); increasing the ease of implementing, monitoring, and improving a policy using the CSP module; and increasing awareness of CSP as a tool to mitigate XSS and other security risks.

gapple’s picture

Status: Fixed » Needs review
ChaseOnTheWeb’s picture

- If someone has a custom subscriber already setting a Permissions-Policy header, I think they're likely aware of FLOC and able to update their policy, and core shouldn't be making the intervention.

If you set $settings['block_interest_cohort'] = FALSE;, then core doesn't try to set the header at all. Doesn't this address your concern? Any contrib module wanting to manage this header could recommend this setting. The settings.default.php documentation could even be updated to something like, "If you don't wish to disable FLoC, or wish to manage the Permissions-Policy header via a module, set this value to FALSE."

Gábor Hojtsy’s picture

Status: Needs review » Fixed

@gapple: this issue already has a commit. To keep credits properly, change records properly and other things, in core we use separate issues for followup changes. If this issue needs to be rolled back, that would necessitate reopening the issue. Please open a followup for further changes.

effulgentsia’s picture

I agree with #82. In addition to that,

If someone has a custom subscriber already setting a Permissions-Policy header, I think they're likely aware of FLOC

I disagree with that assumption. If there's a subscriber that sets Permissions-Policy: geolocation=(), I don't see why we should assume that it's expressing any awareness of or opinion about FLOC. And in the absence of it expressing an opinion about FLOC, or $settings['block_interest_cohort'] expressing an opinion about it, we should implement Drupal's default, which is to disable it. If that subscriber does intend to express an opinion about FLOC, it can do so with Permissions-Policy: geolocation=(), interest-cohort=(*) if it wants FLOC enabled for all origins, or it can set a specific list of origins if it wants that.

rowan_m’s picture

Chrome DevRel here, I understand you've already added this to the beta but I'd like to add some context and sources for ongoing discussion.

I don't see the FLoC proposal itself linked in this discussion, so I'd certainly recommend reading through for appropriate context to any implementation: https://github.com/WICG/floc

FLoC is currently in origin trial which is a mechanism Chrome uses to allow developers to test early proposals in real world conditions with a limited percentage of Chrome users. That means that nothing about FLoC is final, including the site opt-out mechanism via Permissions-Policy. Origin trials are explicitly designed to have an end point and gap between the experiment and any stable functionality expressly to prevent sites relying on origin trial functionality in production. You can read more on the origin trial details here: https://developer.chrome.com/blog/floc/

As such, I would not recommend making core changes based on an origin trial. Given you've already committed code, it would make sense to proactively raise a new issue to track this as the trial progresses and watch for any necessary changes.

It's also worth noting that the origin trial inclusion criteria for a site being used as part of a user's FLoC are: does the site use FLoC itself or does the site use any ads-tagged resources. The inclusion of sites with ads-tagged resources was a way of approximating a selection of sites that are already using third-party cookies in order to deliver interest-based advertising. Again, while an origin trial means no details are fixed, a stable version of FLoC would be far more likely to be opt-in, e.g. using FLoC means the site is included in the FLoC calculation.
What this means is that a default Drupal site is not included in the FLoC origin trial. The only way a Drupal site would be part of the FLoC origin trial is if it either calls the API or if it includes ads-tagged resources.

The effect of setting this header is purely that it prevents a developer from making use of FLoC on their own site without updating the configuration first.

To help understand what FLoC is trying to achieve, it may be useful to look at it in the context of drupal.org. If I arrive as a fresh visitor to the site, I'm presented with the banner, "Can we use first and third party cookies and web beacons to understand our audience, and to tailor promotions you see?" The third-parties that drupal.org adds to the site use a cookie to identify an individual user as they browse across different sites.

FLoC is exploring the option of enabling the use case of tailored promotions without requiring third-parties to track individual users. Instead, the browser is able to present a shared FLoC cohort instead. Any given FLoC cohort is shared across thousands of other users. Instead of needing to track an individual, third-parties can look at the aggregate behaviour of the FLoC cohort instead.
So, the opt-out here raises the barrier for developers to experiment with and contribute to a proposal for the web platform that's focused on improving privacy. Conversely, adding services with individual cross-site tracking to a Drupal site faces no such friction.

Additionally, it's worth understanding the Permissions-Policy header in more detail. This is the format that Feature-Policy is migrating to and the syntax more closely resembles that of Content-Security-Policy. The intent is to allow a site to expressly allow access to specified functionality to listed origins. The module from @gapple looks like a solid base here. I would expect use of the Permissions-Policy header to continue to grow, it's required for functionality such as delegating access to User-Agent Client Hints as they start to replace the user-agent string.

Very happy to discuss this more, but to sum it up: default Drupal sites are not included in FLoC and adding the Permissions-Policy to the core on the basis of an origin trial is not advised.

rachel_norfolk’s picture

Thanks for the information, from a Chrome DevRel point of view.

If, at the end of the trial, there are changes that need to be made, then we would welcome a follow-up issue.

gapple’s picture

I've opened a new issue: #3218139: Stop altering existing Permissions-Policy header in FinishResponseSubscriber

My primary concern was my second point:

non-core service definitions are loaded after core's, and that will cause module event subscribers with the same priority to execute later. As a result, core will (1) most likely never execute the append line of code, and (2) always have its header overwritten by any other implementation anyways

This results in a section of code that is normally unreachable, but with the potential for unexpected behaviour in non-typical circumstances.

--

If you set $settings['block_interest_cohort'] = FALSE;, then core doesn't try to set the header at all. Doesn't this address your concern? Any contrib module wanting to manage this header could recommend this setting.

A module which wants to ensure full control over the header can set its subscriber to priority -1 so that it's guaranteed to execute later, and modify/replace/remove as desired.

There's an issue on the Permissions Policy module looking for feedback on the expected behaviour when the module's configuration is disabled or empty, but it ignores core's attempted modification of any existing value because it's not relevant: #3216790: Overriding core header value in certain cases

I don't see a reason for a module subscriber that sets a Permission Policy to specify a higher priority so that it executes before core's subscriber, which is the one case that it would allow core to append to the existing value. (Instead of just unconditionally overwriting core's default, and adding interest-cohort or not as desired)

--

Some more context on my statement

If someone has a custom subscriber already setting a Permissions-Policy header, I think they're likely aware of FLOC

  • Though it hasn't received significant changes over the last ~year, the Permissions Policy spec is still in working draft status, so implementing it now still requires awareness of potential changes in the definition, and implementation by browsers
  • The Structured Fields spec which it relies on was recently published as an RFC in February 2021. (I don't think any changes over the previous year would have impacted Permissions-Policy, but the potential was there up until publication)
  • The Permissions-Policy spec doesn't define the available policy-controlled features, since other specs can introduce new features at any time (a reference document lists current features)
  • There is currently not a mechanism to disable features by default
  • The Feature Policy module which implements the predecessor header name and value serialization has slowly grown to ~100 installs

My inference from that is that currently there's a limited number of people implementing a Permissions Policy header, and they are likely to be relatively informed early adopters.
If their intent is to implement a strict policy, it is necessary to periodically review the available features to block new ones.
And FLoC and interest-cohort have received some pretty prominent coverage that I think would have caught the attention of anyone who has already implemented a policy.

But relevant to my earlier point, I don't think in either of the cases that someone currently has implemented their policy through web server config or through a custom response event subscriber would the core code to modify an existing value on the response object ever execute, and the value now set by core would just be overwritten by the custom value.

--

A tangent - this issue highlighted some likely unexpected/undesired behaviour in how core currently sets some other headers in the same subscriber: #3214208: FinishResponseSubscriber could create duplicate headers

nod_’s picture

izmeez’s picture

@nod Thanks for the link. Interesting detailed article. Bottom line is targeted ads are wrong! The way they're done now and the proposed cohort methods are all wrong. People who use the Internet are not subjects to be treated as data and profiled. This sort of data is an addiction. We should go back to generic ads. Let the user decide if it has merit; no coercion, no selection, no targeting.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

anoopjohn’s picture

Google has killed FLoC. So should this ability and the corresponding config remain in core. Browsers have started stopping support for this and throwing errors around the header.

#3284706: Error in Edge browser - Error with Permission-policy header: origin trial controlled feature not enabled 'interest cohort'

rachel_norfolk’s picture

Category: Task » Bug report

Good question. Regardless of the answer, the correct thing to do would be to create a follow up issue, referencing this one, to propose that.

rootwork’s picture

Graham Leach’s picture

Hello,

I am getting the following error in the CHROME console (press F12 to see it) with respect to my D7U3 site:

Error with Permission-Policy header: Origin trial controlled featured not enabled: 'interest-cohort'.

I was able to remove this error by adding the following line:

$conf['block_interest_cohort'] = FALSE;

To the end of:

/sites/default/settings.php

After the line was added, I also needed to perform a cache flush and reload of the page.

The error then disappeared from the console.

Cache Flush:

admin -> Flush all caches

Reload:

<CTRL-F5>