Christoph Schwering | eba114c | 2023-08-16 18:17:55 | [diff] [blame] | 1 | # Autofill across iframes |
| 2 | |
| 3 | Chrome Autofill fills in frame-transcending forms like the following pseudo-code |
| 4 | example. |
| 5 | |
| 6 | ``` |
| 7 | <!-- Top-level document URL: https://merchant.example/... --> |
| 8 | <form> |
| 9 | Cardholder name: <input id="name"> |
| 10 | Credit card number: <iframe src="https://psp.example/..." allow="shared-autofill"><input id="num"></iframe> |
| 11 | Expiration date: <input id="exp"> |
| 12 | CVC: <iframe src="https://psp.example/..." allow="shared-autofill"><input id="cvc"></iframe> |
| 13 | <iframe src="https://ads.example/..."><input id="account"></iframe> |
| 14 | </form> |
| 15 | ``` |
| 16 | |
| 17 | This applies to address and payment information, but not to passwords. |
| 18 | |
| 19 | ## The security policy |
| 20 | |
| 21 | An autofill fills a form control *candidate* only if one of the following is true: |
| 22 | |
| 23 | - the autofill's origin and the *candidate*'s origin are the [same origin]; |
| 24 | - [shared-autofill] is enabled in the *candidate*'s [node document] and one of |
| 25 | the following is true: |
| 26 | - the autofill's origin and the top-level origin are the [same origin]; |
| 27 | - the candidate's origin and the top-level origin are the [same origin] and the |
| 28 | *candidate*'s autofill value is non-sensitive. |
| 29 | |
| 30 | The terminology used above is defined in the [appendix](#appendix-terminology). |
| 31 | |
| 32 | This policy is the [eligibility for autofill] definition plus the additional |
| 33 | "... and one of the following is true" conjunct in the [shared-autofill] clause. |
| 34 | |
| 35 | The policy is implemented in [FormForest::GetRendererFormsOfBrowserForm()]. |
| 36 | |
| 37 | ## The rationale |
| 38 | |
| 39 | The example form above exhibits a common pattern: at the time of writing, about |
| 40 | 20% of the payment forms on the web span multiple origins. Most commonly, the |
| 41 | cardholder name field's origin is the top-level origin, whereas the credit card |
| 42 | number is in a cross-origin iframe hosted by the payment service provider (PSP). |
| 43 | |
| 44 | These iframes are typically styled so that they seamlessly integrate with the |
| 45 | merchant's page -- the user is not made aware that multiple frames and origins |
| 46 | are involved. Yet the different origins isolate the payment information from the |
| 47 | merchant's website, which helps them comply with the payment card industry's |
| 48 | data security standard (see Section 2.2.3 of the [PCI-DSS best practices]). |
| 49 | |
| 50 | Chrome Autofill's objective is to fill fields that the user expects to be |
| 51 | filled, even if those fields cross origins, while protecting the user against |
| 52 | possibly malicious sub-frames. Intuitively, we support two "directions": |
| 53 | |
| 54 | - "Downwards": An autofill may fill fields in descendant documents where |
| 55 | [shared-autofill] is enabled. In our example, an autofill initiated on the |
| 56 | cardholder name field may fill the credit card number field. |
| 57 | - "Upwards": An autofill may fill certain values in ancestor documents. In our |
| 58 | example, an autofill initiated on the credit card number field may fill the |
| 59 | cardholder name field. |
| 60 | |
| 61 | We restrict the values that may be filled "upwards" especially to prevent |
| 62 | leaking sensitive payment information -- credit card numbers and CVCs that the |
| 63 | PCI-DSS intends to protect -- into the merchant's page. The "non-sensitive" |
| 64 | values that we allow to be filled "upwards" are credit card types, cardholder |
| 65 | names, and expiration dates. |
| 66 | |
| 67 | The terms "upwards" and "downwards" are imprecise: our security policy doesn't |
| 68 | refer to the [top-level traversable]'s [node document], but rather to its |
| 69 | [origin], the top-level origin. This way, Autofill works the same when, for example, |
| 70 | the cardholder name is hosted in a same-origin iframe: `<iframe |
| 71 | src="https://merchant.example/..."><input id="name"></iframe>`. |
| 72 | |
| 73 | Our security policy does not allow "upwards" or "downwards" filling to and from |
| 74 | arbitrary documents. It only allows filling "upwards to" main-origin documents |
| 75 | and "downwards from" main-origin documents. This simplifies reasoning about the |
| 76 | security policy as well as the implementation, and is still sufficient for |
| 77 | real-world payment forms. |
| 78 | |
| 79 | The following table illustrates which fields may be filled in our example form |
| 80 | depending on the autofill's origin: |
| 81 | |
| 82 | | Autofill's origin | `name` | `num` | `exp` | `cvc` | `account` | |
| 83 | |----------------------------|:--------:|:--------:|:--------:|:--------:|:---------:| |
| 84 | | `https://merchant.example` | ✔ | ✔ | ✔ | ✔ | ✖ | |
| 85 | | `https://psp.example` | ✔ | ✔ | ✔ | ✔ | ✖ | |
| 86 | | `https://ads.example` | ✔ | ✖ | ✔ | ✖ | ✔ | |
| 87 | |
| 88 | ## Appendix: Terminology |
| 89 | |
| 90 | An *autofill* is an operation that fills one or many form control elements in |
| 91 | the [fully active descendants of a top-level traversable with user attention]. |
| 92 | An autofill can only be initiated on a [focused] form control element. |
| 93 | |
| 94 | An autofill *fills a form control* if it changes the form control's [value]. The |
| 95 | value after the autofill is the *form control's autofill value*. |
| 96 | |
| 97 | A form control's autofill value is *non-sensitive* if it is a credit card type, |
| 98 | a cardholder name, or a credit card expiration date. |
| 99 | |
| 100 | A *form control's origin* is its [node document]'s [origin]. |
| 101 | An *autofill's origin* is the [focused] form control's origin. |
| 102 | The *top-level origin* is the [top-level traversable]'s [active document]'s [origin]. |
| 103 | |
| 104 | *[shared-autofill] is enabled in a document* if the [Is feature enabled in |
| 105 | document for origin?] algorithm on [shared-autofill], the document, and the |
| 106 | document's [origin] returns `Enabled`. |
| 107 | |
| 108 | *TODO*: Update link to [eligibility for autofill] once the |
| 109 | [PR](https://github.com/whatwg/html/pull/8801) is closed. |
| 110 | |
| 111 | [FormForest::GetRendererFormsOfBrowserForm()]: https://source.chromium.org/chromium/chromium/src/+/main:components/autofill/content/browser/form_forest.cc;l=618-623;drc=94fbbc584c5d42f0097a9cb28b355853d2b34658 |
| 112 | [active document]: https://html.spec.whatwg.org/#nav-document |
| 113 | [eligibility for autofill]: https://schwering.github.io/html/#eligible-for-autofill |
| 114 | [Is feature enabled in document for origin?]: https://w3c.github.io/webappsec-permissions-policy/#algo-is-feature-enabled |
| 115 | [focused]: https://html.spec.whatwg.org/#focused |
| 116 | [fully active descendants of a top-level traversable with user attention]: https://html.spec.whatwg.org/#fully-active-descendant-of-a-top-level-traversable-with-user-attention |
| 117 | [same origin]: https://html.spec.whatwg.org/multipage/browsers.html#same-origin |
| 118 | [node document]: https://dom.spec.whatwg.org/#concept-node-document |
| 119 | [origin]: https://dom.spec.whatwg.org/#concept-document-origin |
| 120 | [PCI-DSS best practices]: https://www.pcisecuritystandards.org/ |
| 121 | [shared-autofill]: https://schwering.github.io/shared-autofill/ |
| 122 | [top-level traversable]: https://html.spec.whatwg.org/#top-level-traversable |
| 123 | [value]: https://html.spec.whatwg.org/#concept-fe-value |