Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Fenced Frame read-only mode for payment buttons #15

Open
maxlgu opened this issue Feb 15, 2022 · 34 comments
Open

Use Fenced Frame read-only mode for payment buttons #15

maxlgu opened this issue Feb 15, 2022 · 34 comments

Comments

@maxlgu
Copy link

maxlgu commented Feb 15, 2022

Motivation

It’s observed that Payment Service Providers decorate their buttons with card info to increase conversion rate. This card info often includes the network icon and a part of the card number. This depends on the 3p cookie which will get deprecated under the Privacy Sandbox. In order to retain the feature, this issue proposes using Fenced Frame as a work around, and proposes a read-only mode to Fenced Frame to support this use case.

Fig: Example payment button structure

Use outside of payments

The proposal described here does not need to be restricted to payments. Any form of third-party button that wishes to show personalized information to a user, without leaking that information to the host site, could use it. Other examples may include login buttons (e.g., 'Login as Alice') or social media buttons.

High level

A typical design of the button involves:

  • the merchant page of origin A, which has the payment button to capture the click.
  • an iframe of origin B, shown under the button, displaying the card information.
  • the 3p cookie, which provides the iframe with the user id.
  • a server of origin B, where the iframe requests the user’s card info.
    Since the 3p cookie will be deprecated, this design will lose a place to store the user id, and thus break the flow.

This issue proposes a new read-only mode for the Fenced Frame and a new design for the button depending on this mode. In our new design, the iframe on the merchant page is replaced with a Fenced Frame in a read-only mode. The read-only mode has a two-state design, which allows Fenced Frame to read 3p cookies without the risk of the cookie leaking out of the frame.

Upon creation, the Fenced Frame starts its life in state one, where the Fenced Frame has network access but no cookie access. The browser requests the main document and the subresources. After the load is finished, the Fenced Frame transitions to state two, where the access to the network is revoked but the 3p cookie is available to read. The Fenced Frame then reads the card info from the cookie and displays it.

Fig: Comparison of two payment button implementations.

Details

How to allow the button to display card numbers only on trusted merchants?

In the past, this was often achieved by attaching the merchant’s url as the “referrer” header to the payment server. Since the Fenced Frame doesn’t have the “referrer”(TODO: quote needed), we propose attaching the “eTLD+1” of the top level (in the frame hierarchy) page as a new “top-level-site” header to the navigation request. Upon receiving this header, the payment server can decide whether to display the card number to this top-level site.

Fenced Frame has no cookie access in state one.

In order to prevent the cookie from leaking to the payment server, the fenced frame’s navigation request should not carry the “Cookie” header.

Fenced Frame should close all output channels in state two.

The fenced frame in state two is allowed to take input from the unpartitioned cookie (read CHIP to distinguish between partitioned and unpartitioned cookie), and its url parameters, but its output channels are revoked to prevent the cookie from being exfiltrated. These channels include:

  • write cookie
  • send network requests
  • postMessage

How the embedder can pass information into the Fenced Frame.

The embedder can pass info into the Fenced Frame through URL parameters. This enables the use cases that the embedder may want to style the payment button to make it consistent with the embedder. Note that postMessage between the Fenced Frame and the embedder in both directions is not allowed.

How the Fenced Frame can display different network icons based on the card.

The payment server can send all of the network icons as subresources of the page, setting them to hidden by default. The 3p cookie should record the card network id. The Fenced Frame can then use the network id to decide which network icon to show.

Known issues

The card info data in the cookie may get stale.

Once the card info data gets written into the unpartitioned cookie, it will need to wait until the user revisits the payment page in 1st party context to be able to update it. This poses the issue that the card info in the cookie may get stale in the meantime. For example, if the user has removed the card from their account somehow, the button would still show the removed card’s info. Users could visit the payment page in 1st party context when they visit the payment page directly, or when they click the button to open the payment popup / payment handler.

@rsolomakhin
Copy link

What's the up-to-date status of this proposal in terms of whether it is ready for developer testing?

@madmath
Copy link

madmath commented Feb 18, 2022

The fenced frame in state two is allowed to take input from the unpartitioned cookie

If I understand correctly, since State One has no Cookie header and State Two has no network access, this means that State Two only has access to previously-set client-side cookies (non-HttpOnly, accessed through document.cookie). I have three questions/observations:

(1) Are there any announced plans to limit the lifetime of client-side cookies vs HttpOnly cookies? Apple heavily restricts the lifetime of client-side cookies as part of ITP, for example.

(2) Inherently these client-side cookies do not seem as safe (as any script in the frame, such as 3P libraries like Google Analytics or jQuery or extensions, could read them). When I say "not as safe", I'm not necessarily talking about State Two's sandbox which doesn't allow exfiltration (good design), but rather in every other context where paymentmethod.com is running in a normal frame. In other words, as I understand it, to make State Two work, we would have to set a client-side cookie on paymentmethod.com with PII in it, which could be picked up by other scripts accessing document.cookie on the page. Would you then perhaps recommend encrypting these values, so only the fenced frame can decrypt them?

(3) CHIPS proposal mentions "in the long term these [partitioned] cookies will be the only cookies available in cross-party contexts." Are we to understand that this would be a carve-out in that policy, potentially?

@maxlgu
Copy link
Author

maxlgu commented Feb 18, 2022

@DCtheTall should be able to answer (1) and (3).

For (2), this is a good question. I didn't realize this problem. This proposal did assume that PSP would write unencrypted PII into the cookie.

Would you then perhaps recommend encrypting these values, so only the fenced frame can decrypt them? This seems like a good direction to think about it. Could you elaborate the idea?

@madmath
Copy link

madmath commented Feb 18, 2022

Would you then perhaps recommend encrypting these values, so only the fenced frame can decrypt them? This seems like a good direction to think about it. Could you elaborate the idea?

Honestly, thinking about this more, this is tricky. The goal essentially being to write a sensitive value to the cookie that could only be read in the context of a Fenced Frame (which can be trusted not to exfil). For encryption we'd need a per-user key to encrypt/decrypt, and it seems hard to provide that key in Fenced Frame State One (cookieless request). Also, a global decryption key served in all fenced frames doesn't really work either because that could leak by inspection.

I'm curious if perhaps an additional cookie attribute (or a variant of SameSite attribute) could help. So paymentmethod.com in a 1P context could return a response with Set-Cookie: ...; SameSite=Fenced, which would make sure that the cookie is written during the 1P interaction, but only shows up as a client-side cookie in document.cookie for fenced frames, and not anywhere else. Or perhaps a different attribute altogether would also work.

@maxlgu
Copy link
Author

maxlgu commented Feb 21, 2022

@mikewest could you comment whether "SameSite=Fenced" is do-able? thanks!

@mikewest
Copy link
Member

I don't feel like I understand the proposal. For instance, wouldn't a "read-only" mode need to prevent the form submission, since that's a pretty clear mechanism for communication? Likewise, doesn't the privacy sandbox assume that we're going to be breaking communication channels like the link decoration between the merchant site and the fenced frame?

If you end up needing new cookie syntax or behaviors, I expect we could work something out, though we'd probably not want to use the SameSite attribute to do so. But it's not clear to me what the threats are that y'all want to deal with, and how they fit into Fenced Frames' model. I'd suggest chatting with @shivanigithub and/or @domfarolino.

@liquangumax
Copy link

liquangumax commented Feb 21, 2022

wouldn't a "read-only" mode need to prevent the form submission, since that's a pretty clear mechanism for communication

Just to be clear, are you concerned that form submission can be used to exfiltrate whatever saved in cookie at stage two? It won't be possible because cookie is available only after the network is revoked, and form submission depends on network.

doesn't the privacy sandbox assume that we're going to be breaking communication channels like the link decoration between the merchant site and the fenced frame

Sorry for being not clear in this proposal. The full explainer of the read-only mode is posted here. It explains that in read-only mode, the link decoration is safe to be retained, please read "Information flow and privacy model" of "Read-only". The basic idea is that the data carried in link decoration cannot be joined with the data of the unpartitioned cookie, so we can keep link decoration for read-only mode.

I'd suggest chatting with @shivanigithub and/or @domfarolino.

Actually, @shivanigithub is the main contributor of this proposal (thanks, Shivani!).

@maxlgu
Copy link
Author

maxlgu commented Feb 21, 2022

The test page lives here: https://maxlgu.github.io/pr/fencedframe/. You will find the instructions and the video demo in the test page. Note that we are still trying to add a flag (check the status) so that developers can try it out.

@mikewest
Copy link
Member

wouldn't a "read-only" mode need to prevent the form submission, since that's a pretty clear mechanism for communication

Just to be clear, are you concerned that form submission can be used to exfiltrate whatever saved in cookie at stage two? It won't be possible because cookie is available only after the network is revoked, and form submission depends on network.

Yes. Doesn't that mean that clicking on the button wouldn't work? I might be missing something. :)

The full explainer of the read-only mode is posted here.

I would love to see a more detailed specification for this. Since the proposal seems to make it possible for an execution context to transition from one storage partition to another, it seems like there are some potential areas for leakage that could allow persistence (network cache and service workers come to mind; y'all have likely thought of others).

The basic idea is that the data carried in link decoration cannot be joined with the data of the unpartitioned cookie, so we can keep link decoration for read-only mode.

As a thought experiment, it seems to me that this use case really only requires rendering some text in a way that the containing page can't read, while supporting some level of customization for the text's display. If that's the case, I wonder if we could avoid some complexity of swapping between states by asking the payment provider to hand over a static bundle of its supported states for a specific user (which, for gPay, looks to be ~500 or so), and create a mechanism for loading packaged resources in a completely isolated frame (no network, no storage, no swapping from one state to another, (heck, no JavaScript if we can get away with it!)).

That seems simpler to reason about?

@rjacoby
Copy link

rjacoby commented Feb 22, 2022

I wonder if we could avoid some complexity of swapping between states by asking the payment provider to hand over a static bundle of its supported states for a specific user

I think for our (Shopify's) purpose we really need some piece of personal proof in the fenced frame, along the lines of @maxlgu 's partial credit card number example or the partial name I mentioned. This gives the user signal that there's a valuable acceleration about to happen if you click on that payment button b/c it knows who you are and you skip entering a ton of info.

I don't think even a large set of static states accomplishes that - "You're logged in to Shop Pay" doesn't really prove much to the user.

@mikewest
Copy link
Member

I don't think even a large set of static states accomplishes that - "You're logged in to Shop Pay" doesn't really prove much to the user.

Presumably the static state would be a package that's delivered to the user in a first-party context, and the fenced frame would reach into that first-party partition to access it (just as this propsal suggests that the personal info be set in a cookie from a first-party context).

@maxlgu
Copy link
Author

maxlgu commented Feb 22, 2022

Doesn't that mean that clicking on the button wouldn't work?

Good question. The design is that the button lives on the embedder side. If you read the diagram at the top, you will see the overlay "click-jack" the button click, so the fenced frame doesn't need to capture any click.

Since the proposal seems to make it possible for an execution context to transition from one storage partition to another, it seems like there are some potential areas for leakage that could allow persistence (network cache and service workers come to mind; y'all have likely thought of others).

It's only to transition from no-cookie to the unpartitioned cookie, not between partitions. Network cache and service worker are good points. I don't see how they can be used for exfiltration though. @shivanigithub could have thoughts about these?

asking the payment provider to hand over a static bundle of its supported states for a specific user

What this approach misses is that PSP wouldn't be able to tell which merchant the button is embeded on. PSP has a need to showing the card info selectively - it only shows the PII in the context of trusted merchants, see "How to allow the button to display card numbers only on trusted merchants?".

@shivanigithub
Copy link
Collaborator

It's only to transistion from no-cookie to the unpartitioned cookie. Network cache and service worker are good points. I don't see how they can be used for exfiltration though. @shivanigithub could have thoughts about these?

The idea is that storage and network partitioning continue to be in the unique, ephemeral nonce based partition (as detailed here) even in the second state of this proposal. The only unpartitioned state will be the cookies and that access will only be read-only.

@stephenmcgruer
Copy link

Hey folks; no update I'm afraid, but I just wanted to make it clear that we (Chrome Web Payments team) are still interested in this space. Resourcing constraints mean I don't expect to see much activity here in the near future, but I intend to see it picked up in Q3. Our next steps at that point will be to find ways to address the concerns raised here (e.g., where the cached state could be stored securely, etc), as well as consider other alternative approaches that might let us get away from caching/bundling altogether (very attractive idea, but obviously tricky from a privacy perspective :)).

@shivanigithub
Copy link
Collaborator

I'm curious if perhaps an additional cookie attribute (or a variant of SameSite attribute) could help. So paymentmethod.com in a 1P context could return a response with Set-Cookie: ...; SameSite=Fenced, which would make sure that the cookie is written during the 1P interaction, but only shows up as a client-side cookie in document.cookie for fenced frames, and not anywhere else. Or perhaps a different attribute altogether would also work.

@madmath to make sure I understand, what does the "fenced cookie" achieve? Is it that the encryption key can only be read in a fenced frame? So essentially the fenced frame will be able to read both the encryption key and the PII via document.cookie. In that case, I think the PII can also be a "fenced cookie", right?

@shivanigithub
Copy link
Collaborator

cc @igrigorik

@madmath
Copy link

madmath commented Aug 16, 2022

To come back to the goals here (as it's been a while!), we are excited in the the fenced frame mechanism to provide us (payment method) the ability to:

  1. Set some "bundle" of data during a first-party interaction on paymentmethod.com, and it should be fairly personalized ("Continue as Jane", "Pay with ****1111").
  2. Recall that "bundle" of data within a fenced frame of paymentmethod.com, while browsing merchant.com at a top-level.
    1. Privacy-wise, the top-level frame can't see this bundle of data or exfiltrate it, and the fenced frame (and any script running within) can't track the impression either. Granted that last bit is tricky as soon as you allow javascript, because you could leave crumbs that you pick up later in a first-party interaction of paymentmethod.com.

I'm using the term "bundle" as either cookie, service worker "offline" cache, web bundle, ...

@madmath to make sure I understand, what does the "fenced cookie" achieve?

The issue I was raising before is that your State One has no cookie access for the resource fetch, so HttpOnly cookie set in the first party context of paymentmethod.com wouldn't work for that resource fetch. Then in State Two, network access is cut-off so presumably it's fine to have access to document.cookie (a client-side cookie) in the fenced frame. And the way you protect this important PII from other scripts on paymentmethod.com (e.g. google analytics) is to mark it as only being available in SameSite=Fenced.

@shivanigithub
Copy link
Collaborator

@madmath Thanks for the response!

The issue I was raising before is that your State One has no cookie access for the resource fetch, so HttpOnly cookie set in the first party context of paymentmethod.com wouldn't work for that resource fetch. Then in State Two, network access is cut-off so presumably it's fine to have access to document.cookie (a client-side cookie) in the fenced frame. And the way you protect this important PII from other scripts on paymentmethod.com (e.g. google analytics) is to mark it as only being available in SameSite=Fenced.

Summarizing this, the threat model here is trying to avoid paymentmethod.com cookies carrying PII in network requests initiated from other scripts (e.g. google analytics) in a 1P context and the threat is mitigated inside a fenced frame because:

  1. It does not have network access when accessing the PII and thus 3p scripts cannot exfiltrate that information even if they can read it.
  2. it is a document that is opted-in by the server to be loaded in a fenced frame so if it wanted the server can serve a very limited document with no 3p scripts.

Additionally, the cookie that's available in the fenced frame may also include the encryption key in this case for the encrypted PII.

Please feel free to correct/add to this summary.

@madmath
Copy link

madmath commented Aug 23, 2022

I think there's still a bit of drift in what we're saying. I would want the PII to only be made available in the fenced frame, and not available to paymentmethod.com in a 1P context (where other scripts like analytics can live).

@shivanigithub
Copy link
Collaborator

I think there's still a bit of drift in what we're saying. I would want the PII to only be made available in the fenced frame, and not available to paymentmethod.com in a 1P context (where other scripts like analytics can live).

Right, that's what I understood. So we are on the same page.

@shivanigithub
Copy link
Collaborator

Another question...

I'm using the term "bundle" as either cookie, service worker "offline" cache, web bundle, ...

Is having the user's data as well as the html document being locally cached feasible in your use case? If yes, an alternative approach would then be that the fenced frame can be without network from the start instead of in state 2 of read-only mode.

@madmath
Copy link

madmath commented Aug 23, 2022

I think only allowing cached network requests could work, though I'd have questions on controlling the expiry (whereas cookies can be reset, I don't think cache TTL can?).

@shivanigithub
Copy link
Collaborator

more than cached network requests I meant the document and subresources would be part of a web bundle and that web bundle can be refreshed e.g. when the user visits paymentmethod.com as a 1p.

@shivanigithub
Copy link
Collaborator

@igrigorik @madmath
I wanted to clarify the behavior that a fenced-frames based button would have on user click.

To reiterate, for privacy, no information from the cookie can flow out from this FF:

  • either via the navigation url
  • or via communicating anything to the embedding page

The one bit of information that can be transmitted from the browser to the embedding page, is that a click happened on the FF, at which point the javascript in the embedding page would take over, for example, redirect to another page and complete the workflow. Does that align with how you envision the flow working?

/cc @jkarlin @pdelp

@shivanigithub
Copy link
Collaborator

Hey all,
In terms of an update:
Now that the fenced frames initial mode (supporting Protected Audience and Shared Storage) is getting ready to ship (Intent to ship), the team is focusing on figuring out the design for the read-only mode for this use case. We will continue to update here and the explainer for the same.

@dmdabbs
Copy link

dmdabbs commented Sep 14, 2023

@shivanigithub, thank you for presenting this proposal's evolution at TPAC.

The click listener works today because there is an overlay over the cross-origin iframe

Is the thinking that this overlay would be somehow in-built in this feature? I'm trying to wrap my head around how the unrestricted Shared Storage data is caged in the FF.

@shivanigithub
Copy link
Collaborator

Is the thinking that this overlay would be somehow in-built in this feature? I'm trying to wrap my head around how the unrestricted Shared Storage data is caged in the FF.

Thanks for the question! the overlay is basically how payment providers currently have the button set up. Currently, it's essentially a cross-origin payment provider's iframe with the last 4 digits and a button overlaying that iframe so that when the user clicks, the payment provider's script on the top-level merchant page can know that there has been a click and take it from there.
If we replace the iframe with a fenced frame, this setup will continue to work. But additionally, we would want to mitigate any possibility of data leakage from the FF via the click coordinates (which are part of the click event).

@dmdabbs
Copy link

dmdabbs commented Sep 15, 2023

If we replace the iframe with a fenced frame, this setup will continue to work.

I am missing something essential. The new facility also prohibits all top-level navigation events emanating from user interactions within the "locked down" FF in addition to subresource requests?

@shivanigithub
Copy link
Collaborator

I am missing something essential. The new facility also prohibits all top-level navigation events emanating from user interactions within the "locked down" FF in addition to subresource requests?

That is correct. Top-level navigation initiated from the fenced frame is also disabled as part of disabling network access.

@shivanigithub
Copy link
Collaborator

This latest proposal for this use case was presented at TPAC 2023 and here are the slides.
An update: This discussion in the earlier comments used cookies as the broad term for the unpartitioned data access, but the latest design proposes the use of sharedStorage.get() due to the way it more naturally aligns with the requirements here (more details in the slides).

@shivanigithub
Copy link
Collaborator

Please find the details of the proposal here

@dmdabbs
Copy link

dmdabbs commented Dec 15, 2023

Thank you @shivanigithub. Just seeing the I2P and tracking bug.

@shivanigithub
Copy link
Collaborator

Please find the details of the proposal here

@madmath @igrigorik wanted to surface this explainer for you all.

@shivanigithub
Copy link
Collaborator

TAG review for the explainer created here: w3ctag/design-reviews#975

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants