Offscreen doc in iFrame - CSP challenge

446 views
Skip to first unread message

Vince Scafaria

unread,
May 18, 2023, 8:00:39 PM5/18/23
to Chromium Extensions
My Chrome extension helps users to curate their social media experience, choosing which posts to see in a custom feed with a locally-controlled algorithm (fetching posts only from favorite connections, etc.). I'd like to help them fetch data from their social sites by using an offscreen document with the DOM_SCRAPING reason per this suggestion. However, this results in the error: "Refused to frame 'https://thewebsite.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'".  Suggestions? Thanks!

Vince

wOxxOm

unread,
May 19, 2023, 1:40:30 AM5/19/23
to Chromium Extensions, Vince Scafaria
You can use declarativeNetRequest to strip the "frame-ancestors" header, see an example for "X-Frame-Options" in https://stackoverflow.com/a/69177790

Vince Scafaria

unread,
May 19, 2023, 12:00:51 PM5/19/23
to Chromium Extensions, wOxxOm, Vince Scafaria
Thanks for that link. As a first step, I am trying a simpler example of loading an iframe of a url that does not use CSP. I'm starting from the cookbook example for DOM_PARSER. Where the sample calls addExclamationMarksToHeadings in offscreen.js and generates a string, I instead generate an iframe pointed at my target. I then add a content_scripts node to the manifest with a content.js and "matches" for the iframe source. When I add debug code to content.js to see if it gets loaded successfully such as writing to storage.local or console log etc., I come up empty. Do you have an example of parsing out of an iframe and/or how to add the "hello world" code needed to the cookbook sample? Thanks! (Then next step will be the CSP per your answer).

wOxxOm

unread,
May 19, 2023, 12:16:45 PM5/19/23
to Chromium Extensions, Vince Scafaria, wOxxOm
I guess you didn't add "all_frames": true to content_scripts declaration.

Note that in case this content script shouldn't run in normal sites outside your offscreen document you shouldn't declare it in manifest.json, but instead do it temporarily via chrome.scripting.registerContentScripts and unregisterContentScripts afterwards.

To make the content script run only inside your iframe you can add a dummy random id to the URL and use it when registering the content script: let u = new URL(url); u.searchParams.set(Math.random(), '') url = u.href; Theoretically an unknown parameter may be rejected by some site but it's unlikely. Additionally, wrap the entire content script in a condition: if (location.ancestorOrigins.contains(chrome.runtime.getURL('').slice(0, -1)) { ..... }

Vince Scafaria

unread,
May 19, 2023, 4:48:56 PM5/19/23
to Chromium Extensions, wOxxOm, Vince Scafaria
"all_frames": true got me past the first hurdle. If not for the CSP, I'd be in good shape. 
Now I'm back to trying to actually remove the CSP header. Here is the diff relative to the cookbook sample with my best effort to incorporate your answer. When I review the offscreen.html console it still warns: 
Refused to frame 'https://nitter.net/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'".
Anything else I'm missing? Thanks!

wOxxOm

unread,
May 20, 2023, 12:50:06 AM5/20/23
to Chromium Extensions, Vince Scafaria, wOxxOm
When I inspect the request to that site in devtools I don't see frame-ancestors header. There's only X-Frame-Options. Try removing both.

Jackie Han

unread,
May 20, 2023, 2:20:54 AM5/20/23
to wOxxOm, Chromium Extensions, Vince Scafaria
In your diff, you removed the "frame-ancestors" header, but you should remove the "content-security-policy" and "x-frame-options" header. After that, 'https://nitter.net/' should be loaded. 

Note: some other websites may use additional methods to prevent themselves from being embedded by third parties.
Another note: there is a browser bug https://bugs.chromium.org/p/chromium/issues/detail?id=1247400 when you use devtools to debug dNR.

--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/eb4e37ff-e5f4-4da3-9ccf-735d3d885d13n%40chromium.org.

Vince Scafaria

unread,
May 20, 2023, 6:22:25 AM5/20/23
to Chromium Extensions, Jackie Han, Chromium Extensions, Vince Scafaria, wOxxOm
I now understand that frame-ancestors was part of the content-security-policy header, and that it was the latter that had to be removed. I also added x-frame-options. Lastly, I realized I was missing "host_permissions" in the manifest from the S/O answer. It works now, thank you! 

Simeon Vincent

unread,
May 22, 2023, 5:11:37 PM5/22/23
to Vince Scafaria, Chromium Extensions, Jackie Han, wOxxOm
I just put together a demo that shows a very conservative approach to relaxing a site's CSP headers when embedding that site in an extension frame. 


I am extremely hesitant about completely removing security protections like CSP. Rather than completely remove the CSP header, I use a dynamic dNR rule to add the extension's origin to the site's frame-ancestors directive if it is present.

While this approach works, IMO if the extension has host permissions for a site, the extension platform should ignore the extension's origin for the purpose of frame-ancestors evaluation. I've opened an issue to request this behavior: https://crbug.com/1447888.

Simeon - @dotproto


Vince Scafaria

unread,
May 24, 2023, 8:04:50 AM5/24/23
to Chromium Extensions, Simeon Vincent, Chromium Extensions, Jackie Han, wOxxOm, Vince Scafaria
Thanks, Simeon. You've helped trigger a fresh set of questions for me (as a non-expert in this area). My understanding of CSP is that it's primarily designed to help malicious framing of a website into another that masquerades as it, thereby allowing clickjacking. Meanwhile, the use case is for a trusted and non-malicious extension to help the user scrape content that they have access to (in some cases only while logged in). And the headers are only modified when loaded in the extension's offscreen context. So two points I don't understand are:

(a) why the offscreen feature requires a frame in the first place (i.e. why it's a Chrome requirement), and 
(b) why removing the CSP entirely for the offscreen page introduces a risk that Simeon seeks to mitigate. 

Context: My use case is to try to help the user curate their most relevant social data from across sites where they are logged in and have access. To use the example of Twitter, there is no trouble scraping the page when it's the active tab and visible. If I apply the rules below, the offscreen console fills with 403 exceptions. (It's certainly possible this is due to other controls on their end, e.g. visibility as opposed to being framed in, though I can't verify that because the only way to load them offscreen is via a frame). Thanks! 

Rules used:

action: {
      type: 'modifyHeaders',
      responseHeaders: [
        {header: 'X-Frame-Options', operation: 'remove'},
        {header: 'Content-Security-Policy', operation: 'set', value: ''},
        {header: 'Cross-Origin-Resource-Policy', operation: 'set', value: 'cross-origin'},
        {header: 'Cross-Origin-Embedder-Policy', operation: 'set', value: 'require-corp'}
      ],
    }
 

Simeon Vincent

unread,
May 24, 2023, 3:54:13 PM5/24/23
to Vince Scafaria, Chromium Extensions, Jackie Han, wOxxOm
FWIW I also wouldn't call myself an expert. I'm just a guy that's been working on the web log enough to know that security is hard.

CSP isn't just about preventing clickjacking. Per MDN, "CSP is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks." It does this by giving site authors the ability to control what scripts and resources can be loaded on their websites. That's a bit abstract, so let's look at an example to make it a bit more concrete. 

Supply chain attacks are increasingly common in the web ecosystem. Say I own example.com and integrate with ACME Ads to show ads on my site. I do this by adding their script tag to my site, and their script works by dynamically adding other scripts to the page for specific ads. If an attacker is able to insert a little code into one of the libraries used by an ad, then it's possible for them to hijack the website and steal sensitive user data.

CSP allows me as the site author to say "the only scripts that can run on this page will be served by example.com, ads.acme.com, or cdn.acme.com." The browser is then responsible for enforcing those restrictions. So, even if an attacker is able to get the seed of their exploit chain into a dependency, the code it tries to eval or append to the page won't be allowed to execute.

Simeon - @dotproto

Vince Scafaria

unread,
May 25, 2023, 10:04:02 AM5/25/23
to Chromium Extensions, Simeon Vincent, Chromium Extensions, Jackie Han, wOxxOm, Vince Scafaria
Thanks, Simeon. I installed your demo extension. The first time it rendered a cool painting widget. Then I clicked toggle and it gave a "refused to connect" page. Then I clicked toggle again (and even after remove/re-add) from now on it renders an angry face saying "No frames!". 

I understand and appreciate the added context you provided re preserving CSP as much as possible. Meanwhile, do these headers in my manifest provide fallback protection? (Asking the Chrome team too, since neither of us confesses to be an expert.) Lastly, I think it would be very interesting to hear from the Chrome team about my question "(a)" below (why a frame is required in the first place) and if there is more guidance available so developers can work to provide the most secure experience while allowing users to enjoy an experience like the one I've described. Thank you! 

  "cross_origin_embedder_policy": {
    "value": "require-corp"
  },
  "cross_origin_opener_policy": {
    "value": "same-origin"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
  }

Reply all
Reply to author
Forward
0 new messages