diff --git a/builtins/amp-ad.md b/builtins/amp-ad.md index 0b0a3d14c554..2a280db0162d 100644 --- a/builtins/amp-ad.md +++ b/builtins/amp-ad.md @@ -102,7 +102,7 @@ To enable this, copy the file [remote.html](../3p/remote.html) to your web serve ``` -The `content` attribute of the meta tag is the absolute URL to your copy of the remote.html file on your web server. This URL must use a "https" schema. It is not allowed to reside on the same origin as your AMP files. E.g. if you host AMP files on "www.example.com", this URL must not be on "www.example.com" but e.g. "something-else.example.com" is OK. The reason for this limitation is that AMP documents may be served by AMP caches on different origins. If the iframe is never on the same origin as the parent frame, it becomes less likely that one accidentally deploys code that relies on them being same origin, which would break in the 'AMP cache" scenario. +The `content` attribute of the meta tag is the absolute URL to your copy of the remote.html file on your web server. This URL must use a "https" schema. It is not allowed to reside on the same origin as your AMP files. E.g. if you host AMP files on "www.example.com", this URL must not be on "www.example.com" but e.g. "something-else.example.com" is OK. See the doc ["Iframe origin policy"](../spec/amp-iframe-origin-policy.md) for further details on allowed origins for iframes. ##### Enhance incoming ad configuration diff --git a/extensions/amp-iframe/0.1/amp-iframe.js b/extensions/amp-iframe/0.1/amp-iframe.js index 1de7a240d3b8..713ce435f38a 100644 --- a/extensions/amp-iframe/0.1/amp-iframe.js +++ b/extensions/amp-iframe/0.1/amp-iframe.js @@ -49,7 +49,8 @@ export class AmpIframe extends AMP.BaseElement { !((' ' + sandbox + ' ').match(/\s+allow-same-origin\s+/i)) || (url.origin != containerUrl.origin && url.protocol != 'data:'), 'Origin of must not be equal to container %s' + - 'if allow-same-origin is set.', + 'if allow-same-origin is set. See https://github.com/ampproject/' + + 'amphtml/blob/master/spec/amp-iframe-origin-policy.md for details.', this.element); return src; } diff --git a/extensions/amp-iframe/0.1/test/test-amp-iframe.js b/extensions/amp-iframe/0.1/test/test-amp-iframe.js index 2c0f24f5eff0..c3bde009e4fe 100644 --- a/extensions/amp-iframe/0.1/test/test-amp-iframe.js +++ b/extensions/amp-iframe/0.1/test/test-amp-iframe.js @@ -278,6 +278,11 @@ describe('amp-iframe', () => { 'allow-same-origin'); }).to.throw(/must not be equal to container/); + expect(() => { + amp.assertSource('https://google.com/fpp', 'https://google.com/abc', + 'Allow-same-origin'); + }).to.throw(/must not be equal to container/); + expect(() => { amp.assertSource('https://google.com/fpp', 'https://google.com/abc', 'allow-same-origin allow-scripts'); diff --git a/extensions/amp-iframe/amp-iframe.md b/extensions/amp-iframe/amp-iframe.md index 2f58f30fa2ff..541e9481aaf6 100644 --- a/extensions/amp-iframe/amp-iframe.md +++ b/extensions/amp-iframe/amp-iframe.md @@ -23,12 +23,12 @@ Displays an iframe. - `amp-iframe` may not appear close to the top of the document (except for iframes that use `placeholder` as described below). They must be either 600px away from the top or not within the first 75% of the viewport when scrolled to the top – whichever is smaller. NOTE: We are currently looking for feedback as to how well this restriction works in practice. - They are sandboxed by default. [Details](#sandbox) - They must only request resources via HTTPS or from a data-URI or via the srcdoc attribute. -- They must not be in the same origin as the container unless they do not allow `allow-same-origin` in the sandbox attribute. +- They must not be in the same origin as the container unless they do not allow `allow-same-origin` in the sandbox attribute. See the doc ["Iframe origin policy"](../../../spec/amp-iframe-origin-policy.md) for further details on allowed origins for iframes. Example: ```html @@ -43,7 +43,11 @@ The attributes above should all behave like they do on standard iframes. ##### sandbox -Iframes created by `amp-iframe` always have the `sandbox` attribute defined on them. By default the value is empty. That means that they are "maximum sandboxed" by default. By setting sandbox values, one can opt the iframe into being less sandboxed. All values supported by browsers are allowed. E.g. setting `sandbox="allow-scripts"` allows the iframe to run JavaScript, or `sandbox="allow-popups allow-popups"` allows the iframe to run JavaScript and open new windows. +Iframes created by `amp-iframe` always have the `sandbox` attribute defined on them. By default the value is empty. That means that they are "maximum sandboxed" by default. By setting sandbox values, one can opt the iframe into being less sandboxed. All values supported by browsers are allowed. E.g. setting `sandbox="allow-scripts"` allows the iframe to run JavaScript, or `sandbox="allow-scripts allow-same-origin"` allows the iframe to run JavaScript, make non-CORS XHRs, and read/write cookies. + +If you are iframing a document that was not specifically created with sandboxing in mind, you will most likely need to add `allow-scripts allow-same-origin` to the `sandbox` attribute and you mights need to allow additional capabilities. + +Note also, that the sandbox applies to all windows opened from a sandboxed iframe. This includes new windows created by a link with `target=_blank` (Add `allow-popups` to allow this to happen). Adding `allow-popups-to-escape-sandbox` to the `sandbox` attribute, makes those new windows behave like non-sandboxed new windows. This is likely most of the time what you want and expect. Unfortunately, as of this writing, `allow-popups-to-escape-sandbox` is only supported by Chrome. See the [the docs on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox) for further details on the sandbox attribute. @@ -62,7 +66,7 @@ Example of `amp-iframe` with `overflow` element: ```html
Read more!
@@ -71,10 +75,10 @@ Example of `amp-iframe` with `overflow` element: Example of IFrame resize request: ```javascript -window.parent./*OK*/postMessage({ +window.parent.postMessage({ sentinel: 'amp', type: 'embed-size', - height: document.body./*OK*/scrollHeight + height: document.body.scrollHeight }, '*'); ``` @@ -96,17 +100,17 @@ It is possible to have an `amp-iframe` appear on the top of a document when the ```html ``` - The `amp-iframe` must contain an element with the `placeholder` attribute, (for instance an `amp-img` element) which would be rendered as a placeholder till the iframe is ready to be displayed. -- Iframe readiness can be known by listening to `onload` of the iframe or an `embed-ready` postmesssage which would be sent by the Iframe document, whichever comes first. +- Iframe readiness can be known by listening to `onload` of the iframe or an `embed-ready` postMessage which would be sent by the Iframe document, whichever comes first. Example of IFrame embed-ready request: ```javascript -window.parent./*OK*/postMessage({ +window.parent.postMessage({ sentinel: 'amp', type: 'embed-ready' }, '*'); diff --git a/spec/amp-iframe-origin-policy.md b/spec/amp-iframe-origin-policy.md new file mode 100644 index 000000000000..4bf49d45ba06 --- /dev/null +++ b/spec/amp-iframe-origin-policy.md @@ -0,0 +1,15 @@ +# Iframe origin policy + +Various AMP features allow loading iframes from arbitrary origins into AMP pages. Examples are the [`amp-iframe`](../extensions/amp-iframe/amp-iframe.md) element and [the custom domain feature of `amp-ad`](../builtins/amp-ad.md#running-ads-from-a-custom-domain). The origin of a URL such as `https://example.com/some/path` is `https://example.com`. See [the HTML5 spec](https://www.w3.org/TR/2011/WD-html5-20110525/origin-0.html#origin) for details. + +These iframes are typically allowed to execute arbitrary JavaScript, but for security reasons they are never allowed to access the AMP document itself using any method besides sending messages via postMessage. + +AMP documents are designed to be accessible both through the web servers and origins where they are hosted and AMP proxy caches (such as cdn.ampproject.org). In the latter case, the iframes would never be on the same origin as the document, because iframes cannot be hosted on the proxy cache. This enforces the security rule from above. + +In the case where iframe and document are hosted by the same party the rule is much less relevant, because the cross iframe access would happen exclusively between HTML pages owned by the same party. Having said that, AMP wants to ensure that AMP documents behave the same whether they are served through a proxy or served from the origin domain. If iframe and document were on the same origin in the latter case, one could accidentally write code that relies on them being on the same origin. Such documents would then break when hosted on the cache. To ensure that direct JavaScript access between AMP document and iframe is never possible, AMP thus enforces that they are not on the same origin. There is one exception to this. `amp-iframe` uses a restrictive [iframe-sandbox](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox) by default. If one does not opt into `allow-same-origin`, then every origin is allowed for the iframe. As soon as you add `allow-same-origin` to the sandbox the origin rules apply. + +In concrete terms this means: If your main site is hosted on `www.example.com`, then you cannot include an iframe from `www.example.com`. Every other origin such as `iframe.example.com` or `assets.example.com` is fine. + +## Security impact + +The above only enforces that documents do not rely on cross-frame access for functionality. There is no guarantee that iframes are never on the same origin as the origin an AMP document is hosted on. One can easily circumvent AMP's not-same-origin-enforcement through redirects, since only the initial URL is tested. diff --git a/src/3p-frame.js b/src/3p-frame.js index 33ba1b741c20..5995389b5178 100644 --- a/src/3p-frame.js +++ b/src/3p-frame.js @@ -274,6 +274,7 @@ function getCustomBootstrapBaseUrl(parentWindow) { // redirect to the proxy origin which is the important one. assert(parseUrl(url).origin != parseUrl(parentWindow.location.href).origin, '3p iframe url must not be on the same origin as the current document ' + - '%s in element %s.', url, meta); + '%s in element %s. See https://github.com/ampproject/amphtml/blob/' + + 'master/spec/amp-iframe-origin-policy.md for details.', url, meta); return url + '?$internalRuntimeVersion$'; }