privacycg / storage-access-headers

Proposal for HTTP headers related to the Storage Access API
https://cfredric.github.io/storage-access-headers/
Creative Commons Attribution 4.0 International
24 stars 2 forks source link

Alternative design: Consider integrating with CORS and HTML and Fetch #3

Closed bvandersloot-mozilla closed 6 months ago

bvandersloot-mozilla commented 11 months ago

Continuing from #2.

I have an idea to remove the additional round-trip and to reuse some infrastructure.

This entails two conceptual changes. 1. Move the activation of storage access to the client, rather than the server. 2. Use CORS preflights to maintain server control over whether or not unpartitioned cookies are ever sent.

To accomplish 1, we don't have a Activate-Storage-Access: header and use a new HTML attribute use-storage-access. This attribute would only be valid on frames or where cross-origin="use-credentials" is present and says to use unpartitioned cookies if the storage-access permission is Granted. This would do some stuff in Fetch that makes it use the right cookie jar (cue Johann's cookie layering).

To accomplish 2, we need to make the requests with unpartitioned cookies not "simple". That forces a preflight which the server can make security decisions on.

We would still need an informational header to inform the server of whether or not the request was unpartitioned, but may not need the detail about inactivity. Maybe reviving something like Dylan's proposals in https://github.com/w3c/webappsec-fetch-metadata/issues/80 would make sense.

Overall I think this is a cleaner design because it treats unpartitioned credentialed requests as an "unsafe thing" in the construction for "unsafe things" we already have.

cfredric commented 11 months ago

I have an idea to remove the additional round-trip and to reuse some infrastructure.

If I understand correctly, this doesn't remove the additional round-trip; it converts it into a CORS preflight instead of a traditional GET. Right? And this is all gated on whether the top-level document used the new HTML attribute, so it requires work/coordination from both the top-level and the embed in order to use this properly.

IIUC, we'd still need some kind of new response header to allow the server to say "please load this content with storage-access activated", in order to skip the additional round-trip for iframes that don't themselves require auth cookies, but which load subresources that do require auth cookies. (This is what the load token does in the current proposal.) And if we still have that response header, then the server needs to know when it's reasonable to use load, so we still need the inactive request header.

So IMO, the design you suggest:

So while it sounds nice to reuse CORS infrastructure, I don't think it actually gains us anything, and it seems to make things worse IMO. WDYT?

bvandersloot-mozilla commented 11 months ago

Sorry- that should say "I have an idea to remove the additional round-trip mechanism...". I was thinking of complexity of the platform as a constraint here.

If I understand correctly, this doesn't remove the additional round-trip; it converts it into a CORS preflight instead of a traditional GET. Right?

Yes, however, now that I look at it again, I am not sure if we need to make these requests non-simple. Simply making these requests protected by CORS should be enough to have an opt-in signal. This would remove the round-trip.

Either: Keeps the load response token, and therefore spreads this feature into both old CORS headers and a new header, instead of handling both in the same response header. Abandons the load response token, and therefore regresses performance by forcing the iframe to execute JS and then refresh (same status quo as today).

Or iframes with "use-storage-access" just set the has storage access bit of their document if they have storage access.

Makes usage harder, since now the top-level page must be modified.

This seems like reasonable usage to me, and probably easier than adding HTTP header-handling for most developers.

This doesn't scale for non-iframe resources that are embedded in many different top-level sites.

This is no worse than cross-origin="use-credentials" which should be how those resources are doing this now, no?

It's inconsistent with the JavaScript ergonomics (since the default allowlist for storage-access permissions policy is "*").

I'll give you this. However, I don't think this is an issue. Forcing better hygiene to use a more performant option is good.

cfredric commented 11 months ago

Yes, however, now that I look at it again, I am not sure if we need to make these requests non-simple. Simply making these requests protected by CORS should be enough to have an opt-in signal. This would remove the round-trip.

The problem with this is that this opt-in signal is coming from the embedding site (the one that embeds the subresource), which is (by assumption) different from the embedded site (the one that supplies the subresource). An HTML attribute doesn't let us conclude that the embedded site is opting in.

Similarly, we can't use use-storage-access to just set the has storage access bit in the iframe's document, since that would let a malicious embedder mount a CSRF attack on a victim iframe, without giving the iframe any choice in the matter.

This seems like reasonable usage to me, and probably easier than adding HTTP header-handling for most developers.

Maybe your opinion has changed based on what I said above (re: security properties), but if we did require a use-storage-access HTML attribute, what does that get us?

Considering that we still need a response header to indicate opt-in from the 3p server (IIUC), I think that additionally requiring the HTML attribute would just allow a top-level document to forbid credentialed cross-site subresource requests (by just omitting the attribute everywhere). IMO, the simpler way to achieve that is to apply a storage-access permissions policy of self to the whole top-level document.

I guess the attribute allows the embedder to be selective about which subresources can use credentials and which can't? But I'm struggling to see the utility of that, considering the user has already given storage-access permission so they do not want to enforce a privacy boundary between this pair of sites.

bvandersloot-mozilla commented 11 months ago

The problem with this is that this opt-in signal is coming from the embedding site (the one that embeds the subresource), which is (by assumption) different from the embedded site (the one that supplies the subresource). An HTML attribute doesn't let us conclude that the embedded site is opting in.

The HTML attribute isn't the opt-in signal I was talking about here. The CORS Access-Control-Allow-Credentials is the third-party's opt in.

The HTML attribute is a little orthogonal, and is just a way to determine which resources should be unpartitioned. I suppose there is a design principle question here: should the client or the server determine which resources are unpartitioned. I favor the client. That is in line with the Javascript API, leaves room to remove the round trip for unpartitioned resources, and it just makes more sense to me that the document makes decisions about when to try to use unpartitioned cookies.

cfredric commented 11 months ago

Ah, I misunderstood. So if I'm understanding correctly, you're suggesting that the browser would always send 3p cookies if the storage-access permission has been granted and the request is CORS-enabled?

(And the browser would probably also send 3p cookies even without CORS if the storage-access permission has been granted and "activated" by a call to requestStorageAccess(), for backward-compat reasons.)

That sounds reasonable to me in terms of privacy/security, and it does avoid the extra round-trip. That's pretty nice.


So the remaining piece is the load token's semantics; I think it would still be good to support Activate-Storage-Access: load (or some other spelling) so that iframes can load their own subresources via credentialed requests immediately.

bvandersloot-mozilla commented 11 months ago

Ah, I misunderstood. So if I'm understanding correctly, you're suggesting that the browser would always send 3p cookies if the storage-access permission has been granted and the request is CORS-enabled?

Not always, but where an HTML attribute asks for them specifically. And that would force CORS mode.

(And the browser would probably also send 3p cookies even without CORS if the storage-access permission has been granted and "activated" by a call to requestStorageAccess(), for backward-compat reasons.)

:+1:

That sounds reasonable to me in terms of privacy/security, and it does avoid the extra round-trip. That's pretty nice.

Thank you!

So the remaining piece is the load token's semantics; I think it would still be good to support Activate-Storage-Access: load (or some other spelling) so that iframes can load their own subresources via credentialed requests immediately.

If the embedee marking the iframe as "use-storage-access" isn't good enough (presumably because only requiring 3rd-party changes is easier in the popular embed use-case?), including a new HTTP Response header for frame content that has the semantics of "Activate-Storage-Access: load" isn't bad. A spelling of Accept-Unpartitioned: ?1 suddenly comes to mind: the server negotiates the headers of future requests.

cfredric commented 11 months ago

Hm, on second thought, one intentional property in my original design was that this didn't require any work on the part of the top-level site; the 3p could manage things entirely by itself with the request header and response header. That makes the headers a little different from CORS, since CORS (and the use-storage-access HTML attribute) requires an opt-in from the top-level, as well as a response header from the 3p.

I could certainly see a world where both designs coexist, so that the top-level site can do some work in order to avoid the extra latency from the additional round trip. But I want to avoid introducing an unnecessary requirement that the top-level site must do that work.

I suppose there is a design principle question here: should the client or the server determine which resources are unpartitioned. I favor the client. That is in line with the Javascript API, leaves room to remove the round trip for unpartitioned resources, and it just makes more sense to me that the document makes decisions about when to try to use unpartitioned cookies.

The principle I've been using is embeddee vs embedder, rather than server vs client. I.e., the embeddee must be the (only) one that determines which of its resources are fetched using unpartitioned cookies, for security reasons. The embeddee already has the ability to do that on the client (via document.requestStorageAccess(), if it has an iframe already), but doesn't have the ability to do it on the server yet (for the cases where it doesn't have an iframe).

And conversely, it's important to not require the embedder's participation, because we're already seeing cases where a cross-site resource is embedded in lots of different top level sites (think any SaaS provider). Updating all of those top level sites is not necessarily feasible, nor is it really necessary.

bvandersloot-mozilla commented 10 months ago

The principle I've been using is embeddee vs embedder, rather than server vs client.

I get that, and I think that is an important principal. How about an HTML vs Fetch distinction? Because to me, fundamentally what you want to do is alter an environment, and in order to do that it would be best to make changes to the HTML rather than the Fetch. Like throwing a use-storage-access on the iframe's <html> element to attempt a permission activation. That would preserve the Activate-Storage-Access: load case, and we could even have an option to force a refresh where the permission is activated.

I see Access-Control-Allow-Credentials and cross-origin="use-credentials" as solving almost this exact problem in the past and think a major change in structure of the solution requires a compelling reason. Zero-change for the embedder is a good reason, but if we can design for it, it would be nice to keep this the same shape. I'd also like to prevent having two ways to do it if we can avoid it too.

cfredric commented 10 months ago

How about an HTML vs Fetch distinction? Because to me, fundamentally what you want to do is alter an environment

I assume you're talking about the embeddee's environment, i.e. the iframe case with Activate-Storage-Access: load?

I see Access-Control-Allow-Credentials and cross-origin="use-credentials" as solving almost this exact problem in the past and think a major change in structure of the solution requires a compelling reason.

Those are also about handling cross-origin requests, but they attack the problem from a slightly different angle. They allow the embedder to ask for credentials to be included in the cross-origin request.

I'm trying to give the embeddee a way to ask for credentials to be included in a request to its origin. The embeddee can't supply the crossorigin attribute, so that doesn't give the embeddee a way to ask for its credentials.

IIUC, CORS fundamentally requires opt-in from both parties (the sender/embedder via cross-origin=...; the recipient/embeddee via Access-Control-Allow-Credentials). So I like the idea of allowing the embedder to include credentials (if the storage-access permission is granted) via cross-origin=... (since that still requires the embeddee's approval eventually, via CORS). But I don't think it's viable to require the cross-origin=... attribute, since that means the embeddee can't unilaterally ask for its credentials.

I'd also like to prevent having two ways to do it if we can avoid it too.

I think we ought to be careful about this principle. The embeddee currently doesn't have any ways of asking for their credentials (unless it's capable of executing JS); so I think we ought to count this header as a distinct capability, rather than an additional way of doing something that's already possible.

I also think it's ok to have more than one way to do something if one way presents better tradeoffs than the other in some cicumstances. E.g., it's fine to provide the load token instead of executing document.requestStorageAccess(), since that allows the client to avoid unnecessary subresource loads and JS execution (presuming that permission has already been granted).

bvandersloot-mozilla commented 10 months ago

How about an HTML vs Fetch distinction? Because to me, fundamentally what you want to do is alter an environment

I assume you're talking about the embeddee's environment, i.e. the iframe case with Activate-Storage-Access: load?

Yep! I'm pointing out that the thing whose state we are changing is a Document, so putting the change in HTML is a closer match to the current semantics where the document calls requestStorageAccess. I'm not saying that this would be hard to spec up, just trying to think where we should add this functionality.

I see Access-Control-Allow-Credentials and cross-origin="use-credentials" as solving almost this exact problem in the past and think a major change in structure of the solution requires a compelling reason.

Those are also about handling cross-origin requests, but they attack the problem from a slightly different angle. They allow the embedder to ask for credentials to be included in the cross-origin request.

I'm trying to give the embeddee a way to ask for credentials to be included in a request to its origin. The embeddee can't supply the crossorigin attribute, so that doesn't give the embeddee a way to ask for its credentials.

Fair! But getting a little creative here: a <meta> tag in the embeddee could take the role of the embeddee's opt in. Like an iframe with <meta name="storage-access" value="blah"> could be the same as Activate-Storage-Access response header in your proposal, with a value for load and another for retry.

We should try to support use cases where the web dev doesn't have control of the server where possible, and I think we can. That and not reinventing a CORS-like mechanism are why I'm trying to help here.

I think we ought to be careful about this principle. The embeddee currently doesn't have any ways of asking for their credentials (unless it's capable of executing JS); so I think we ought to count this header as a distinct capability, rather than an additional way of doing something that's already possible.

Agreed that JS and non-JS ways of doing a thing are distinct capabilities.

I also think it's ok to have more than one way to do something if one way presents better tradeoffs than the other in some cicumstances. E.g., it's fine to provide the load token instead of executing document.requestStorageAccess(), since that allows the client to avoid unnecessary subresource loads and JS execution (presuming that permission has already been granted).

👍, agreed

cfredric commented 10 months ago

Yep! I'm pointing out that the thing whose state we are changing is a Document, so putting the change in HTML is a closer match to the current semantics where the document calls requestStorageAccess.

Ah I see, I misinterpreted!

FWIW, CSP and Permissions Policy are two examples of headers that integrate with the HTML spec, so there's precedent for modifying Document state based on a response header - particularly one related to security.

Fair! But getting a little creative here: a tag in the embeddee could take the role of the embeddee's opt in. [...]

That's an interesting idea! That feels similar to how Content Security Policy supports delivery via a \ tag. I'm not opposed to supporting something like that in the future, to help use cases where the web dev doesn't control the server.

I do also want to support use cases where modifying the content isn't an option, though, (since if the content can be modified, then they have options today; they could just call requestStorageAccess()). If the content can't be modified, then there aren't really options available today, which is part of what I'm trying to build for.

not reinventing a CORS-like mechanism are why I'm trying to help here.

I've been thinking about this more, and I'm becoming convinced that we do have to reinvent a CORS-like thing:

I think this mechanism and CORS are sort of complementary to each other, and have a lot in common. In particular:

WDYT?

johannhof commented 9 months ago

Catching up with this (thanks for the great discussion!), and at the risk of being biased 😆, I think I'm supportive of Chris' direction and ideas here.

Two principles I'd really like to uphold with this proposal:

Since these are both challenges we've heard quite frequently from developers.

I also support the idea of reducing reliance on servers (and server libraries) to support the right headers to use SAA, this could be especially helpful in environments where these headers can't be easily modified (CDNs, I think?). However, it feels orthogonal to the problems being attacked by this proposal, and SAA is already usable with only client-side code.

Overall I'm skeptical about the overloading of CORS to solve storage access opt-ins as a long term web platform solution. As Chris mentioned, it only gets us halfway there in terms of security benefits. We're using it as the top-level opt-in mechanism for rSAFor, which is criticized as insufficient in https://github.com/privacycg/requestStorageAccessFor/issues/30. Besides that, I've also come to realize that overloading these existing concepts for new exciting purposes often causes additional effort and pain for everyone involved, including the folks who maintain CORS and now have to watch out for side effects they didn't design for. cc @arturjanc

There's probably a lot to write about this but I think I'd prefer to not further enshrine it on the web platform until we have more clarity on these questions. We can explore it, though!

These headers (particularly the retry part) will have to handle non-idempotent request methods with a preflight of some kind, same as CORS does.

Unfortunately yes, I think you're right.

bvandersloot-mozilla commented 9 months ago

I am coming around on this being a new header set- the issue specifically being the fallback case. In CORS it is an error and here we would want it to just send without unpartitioned credentials.

Okay, it makes sense that we could have these headers co-defined in meta tags. We can punt on the network-only-ness, and just slap on meta http-equiv definitions at the end.

These headers (particularly the retry part) will have to handle non-idempotent request methods with a preflight of some kind, same as CORS does.

Oof. Looking forward to your writeup on the problem. My gut tells me it may be easiest to forbid retry on non-idempotent request methods, especially due to the change in credentials between the two requests.

cfredric commented 9 months ago

Re: HTML vs Fetch, I also realized that in order to require a change in the HTML (e.g. a <meta> tag or something), there has to be some embedded HTML. I think this unfortunately disqualifies things like the IIIF use case (which embeds images, not iframes). To allow some non-HTML option, I don't think we have a choice but to allow a Fetch-only usage/integration.

Sarrac3873 commented 9 months ago

Continuing from #2.

I have an idea to remove the additional round-trip and to reuse some infrastructure.

This entails two conceptual changes. 1. Move the activation of storage access to the client, rather than the server. 2. Use CORS preflights to maintain server control over whether or not unpartitioned cookies are ever sent.

To accomplish 1, we don't have a Activate-Storage-Access: header and use a new HTML attribute use-storage-access. This attribute would only be valid on frames or where cross-origin="use-credentials" is present and says to use unpartitioned cookies if the storage-access permission is Granted. This would do some stuff in Fetch that makes it use the right cookie jar (cue Johann's cookie layering).

To accomplish 2, we need to make the requests with unpartitioned cookies not "simple". That forces a preflight which the server can make security decisions on.

We would still need an informational header to inform the server of whether or not the request was unpartitioned, but may not need the detail about inactivity. Maybe reviving something like Dylan's proposals in https://github.com/w3c/webappsec-fetch-metadata/issues/80 would make sense.

Overall I think this is a cleaner design because it treats unpartitioned credentialed requests as an "unsafe thing" in the construction for "unsafe things" we already have.

cfredric commented 6 months ago

Continuing to think about this - I was wrong about needing to design a preflight mechanism, similar to CORS. I wrote up an argument why preflights are unnecessary in https://github.com/cfredric/storage-access-headers/commit/adbfd5a12b8381c41daa73f2215e15bba231ee72.

Since CORS preflights are not for protecting the semantics of non-idempotent requests (after all, POST requests aren't preflighted), we don't need to handle non-idempotent requests specially here either; a server can respond with a 4XX and the retry header if desired.

arturjanc commented 6 months ago

There's been a bunch of discussion on this issue (sorry I missed it, I was OOO and never managed to get to this in my post-Christmas backlog) and I think the proposal has changed in the meantime, so just adding a few random thoughts:

  1. Re: CORS: It would be almost okay from a security perspective to attach credentials to the embedder's requests in cors mode to an origin that obtained storage-access permission, as long as the request is made with { credentials: 'include'}. CORS has the necessary opt-ins to make this mostly safe (e.g. the server needs to set the Access-Control-Allow-Credentials: true and Access-Control-Allow-Origin: https://embedding-origin.example headers.
    • The one big caveat here is that CORS allows making non-preflighted POST requests and learn response timings for cross-origin requests (@johannhof the same issues we discussed in the context of CORS for ABA embeds).
    • Because of this, I think it would be safer if we required SAA headers as an opt-in here, rather than piggyback on CORS. This way, the embedder won't be able to make unconstrained requests to the embeddee origin, but only to endpoints that set the Activate-Storage-Access header.
  2. Re: no-cors subresource requests. The good thing is that AFAIK there is no way to make requests with non-idempotent methods (no-cors requests can only be GETs; the only exception are <form> submissions via POST). So I don't think there's anything special that needs to happen for retry here.
  3. Should we even integrate with CORS/Fetch in the first place?
    • I think it's reasonable to have a completely separate header (it's somewhat cleaner conceptually), especially because we want to support loading no-cors resources with credentials without requiring upgrading them to CORS. So the reasons @johannhof mentioned above mostly make sense to me.
    • CORS does solve some problems that we might want to tackle here (e.g. the granular origin-level opt-in for credentialed requests that could be useful for https://github.com/cfredric/storage-access-headers/issues/7). But I think the overlap is limited enough that a separate header might still be simpler.
bvandersloot-mozilla commented 6 months ago
1. Re: CORS: It would be _almost_ okay from a security perspective to attach credentials to the embedder's requests in `cors` mode to an origin that obtained `storage-access` permission, as long as the request is made with `{ credentials: 'include'}`. CORS has the necessary opt-ins to make this _mostly_ safe (e.g. the server needs to set the `Access-Control-Allow-Credentials: true` and `Access-Control-Allow-Origin: https://embedding-origin.example` headers.

   * The one big caveat here is that CORS allows making non-preflighted POST requests and learn response timings for cross-origin requests (@johannhof  the same issues we discussed in the context of CORS for ABA embeds).
   * Because of this, I think it would be safer if we required SAA headers as an opt-in here, rather than piggyback on CORS. This way, the embedder won't be able to make unconstrained requests to the embeddee origin, but only to endpoints that set the `Activate-Storage-Access` header.

This could be addressed with an addition to the fetch's RequestInit, or even adding redefining the RequestCredentials enum to include a new value "include-unpartitioned". Then forcing requests with that specification to be pre-flighted.

3. Should we even integrate with CORS/Fetch in the first place?

[...] because we want to support loading no-cors resources with credentials without requiring upgrading them to CORS.

I missed this argument, but I disagree with this approach. Allowing the use of unpartitioned credentials where you wouldn't otherwise allow partitioned credentials seems unwise. And saving developers the trouble of upgrading their endpoints to CORS is not helpful if we are forcing them to do an equivalent opt-in anyway.

[...] But I think the overlap is limited enough that a separate header might still be simpler.

I think the overlap is significant enough that it would be simpler to integrate 😄

johannhof commented 6 months ago

@bvandersloot-mozilla how would this work with navigational requests and/or requests that the 1P is unable to mark as crossorigin? It feels like the 3P should be able to enforce this mode on its own.

bvandersloot-mozilla commented 6 months ago

[...] because we want to support loading no-cors resources with credentials without requiring upgrading them to CORS.

I missed this argument, but I disagree with this approach. Allowing the use of unpartitioned credentials where you wouldn't otherwise allow partitioned credentials seems unwise. And saving developers the trouble of upgrading their endpoints to CORS is not helpful if we are forcing them to do an equivalent opt-in anyway.

Reading back- I think I missed the point on this one. If I have it right now, these would be no-cors requests that were already with credential mode "include". In which case I take this back!

bvandersloot-mozilla commented 6 months ago

@bvandersloot-mozilla how would this work with navigational requests and/or requests that the 1P is unable to mark as crossorigin? It feels like the 3P should be able to enforce this mode on its own.

I forgot about the no-change-on-the-1p constraint. Combined with my re-read above and the lack of a need for preflights, maybe it would be easier to have independent. I could see either being easier still.

I can't shake the feeling that this is about giving the server the power to permit resource sharing based upon the parameters of the request, which is dead on with CORS's purpose in my mental model.

arturjanc commented 6 months ago

Reading back- I think I missed the point on this one. If I have it right now, these would be no-cors requests that were already with credential mode "include". In which case I take this back!

Yes, in my mental model these are loads via <img>, etc. which by default include credentials (except if third-party cookie restrictions kick in). It seems useful to allow the use of SAA headers for such resources without requiring the 1P to switch them to be loaded via CORS (by adding the crossorigin attribute).

I can't shake the feeling that this is about giving the server the power to permit resource sharing based upon the parameters of the request, which is dead on with CORS's purpose in my mental model.

I see your point, but here we're just talking about granting the capability to send a credentialed request to the resource without exposing the contents of that resource to the requester.

If you opt into CORS you grant the requester access to the bytes of the resource (and the resource load can have credentials if the requester sets credentials=include in Fetch and the server opts in by setting the right ACAC and ACAO headers).

With SAA headers you say "I'm allowing this request to have credentials, but I'm not sharing the contents of the resource with you". E.g. imagine a 3P widget that directly renders the user's profile picture as an image on the embedding page (where the 3P knows the user because it's an IdP or something like that); for loads like that we'd want the image to be loaded with credentials, but without CORS to not expose it to the embedder.

This obviously gets murky because no-cors loads are generally leaky (e.g. images expose their dimensions to the embedder and can be read from the embedder's renderer memory via Spectre, etc). But in terms of the web's security model there's still a difference between allowing embedding and directly exposing the resource to the embedder. CORS is meant for the latter, which is different from the capability to send authenticated requests which SAA headers are trying to allow - IMHO this would be an argument for decoupling this from CORS.

bvandersloot-mozilla commented 6 months ago

But in terms of the web's security model there's still a difference between allowing embedding and directly exposing the resource to the embedder. CORS is meant for the latter, which is different from the capability to send authenticated requests which SAA headers are trying to allow - IMHO this would be an argument for decoupling this from CORS.

Reading this out, I think this distinction is significant and makes sense to me.

bvandersloot-mozilla commented 6 months ago

I have another point, but it strays from integration, so I'm filing #8.

cfredric commented 6 months ago

Thanks all for the discussion. I'll do my best to summarize:

With all that in mind, I think we've reached consensus that CORS ought to be neither necessary nor sufficient for including unpartitioned cookies, and therefore it's best for security (and usability) if the Storage Access Headers are a separate thing, independent from CORS.

I'll tentatively close this issue, but feel free to correct me if I've misunderstood anything.