63

I'm the author of Intab, a Chrome extension that lets you view a link inline as opposed to a new tab. There's not much fancy stuff going on behind the scenes, it's just an iframe that loads the URL the user clicked on.

It works great except for sites that set the X-Frame-Options header to DENY or SAMEORIGIN. Some really big sites like Google and Facebook both use it which makes for a slightly janky experience.

Is there any way to get around this? Since I'm using a Chrome extension, is there any browser level stuff I can access that might help? Looking for any ideas or help!

asked Mar 20, 2013 at 19:20
3
  • 3
    It might be different for extensions, but I know that in javascript there is currently no way of knowing if the load was blocked by X-Frame-Options. In javascript, no error is thrown and no events are triggered when a page load is blocked by X-Frame-Options. Commented Mar 20, 2013 at 19:25
  • 1
    I don't think so its going to be possible. There is a reason why X-Frame-Option is added which is so that the Url cannot be framed in an Iframe which is not in a domain (in case of Same Origin). If somehow u are able to bypass this its a security breach/bug in X-Frame whihc will be fixed in the later version. Also more and more websites are using this option to add that security to their website without doing a lot of stuff:. It would be exciting to see if it can be beaten though. Thats my 2 cents. Commented Apr 9, 2015 at 18:14
  • 6
    @user428747, Chrome extensions should be allowed to do it. They aren't javascript, they are part of the "trusted bundle" which means that they should be considered part of the browser itself. Commented Dec 16, 2016 at 9:45

3 Answers 3

81

This answer is for ManifestV2 and policy-installed MV3 extensions.
For normal ManifestV3 extensions see the other answer(s).

Chrome offers the webRequest API to intercept and modify HTTP requests. You can remove the X-Frame-Options header to allow inlining pages within an iframe.

chrome.webRequest.onHeadersReceived.addListener(
 function(info) {
 var headers = info.responseHeaders;
 for (var i=headers.length-1; i>=0; --i) {
 var header = headers[i].name.toLowerCase();
 if (header == 'x-frame-options' || header == 'frame-options') {
 headers.splice(i, 1); // Remove header
 }
 }
 return {responseHeaders: headers};
 }, {
 urls: [
 '*://*/*', // Pattern to match all http(s) pages
 // '*://*.example.org/*', // Pattern to match one http(s) site
 ], 
 types: [ 'sub_frame' ]
 }, [
 'blocking',
 'responseHeaders',
 // Modern Chrome needs 'extraHeaders' to see and change this header,
 // so the following code evaluates to 'extraHeaders' only in modern Chrome.
 chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
 ].filter(Boolean)
);

In the manifest, you need to specify the webRequest and webRequestBlocking permissions, plus the URLs patterns you're intending to intercept i.e. "*://*/*" or "*://www.example.org/*" for the example above.

woxxom
75.1k15 gold badges161 silver badges164 bronze badges
answered Mar 20, 2013 at 21:12
Sign up to request clarification or add additional context in comments.

33 Comments

Turns out webRequest isn't accessible on content-scripts. I'll have to move it over to a background.
@IanMcIntyreSilber Hello, I'm just wondering if you ever got this to work and if you could possibly teach me if you did.
@RobW X-Frame-Options can be set as a HTML meta element see: javascript.info/tutorial/clickjacking
@GuyKorland I see. Chrome extensions cannot modify response bodies, so you're out of luck.
@TaiwanGrapefruitTea Header modifications by extensions don't show up in the devtools. If you want to see whether the header modification was successful, take a look at chrome://net-internals/#events.
|
33

ManifestV3 example using declarativeNetRequest

See also the warning at the end of this answer!

manifest.json for Chrome 96 and newer,
doesn't show a separate permission for "Block page content" during installation

 "minimum_chrome_version": "96",
 "permissions": ["declarativeNetRequestWithHostAccess"],
 "host_permissions": ["*://*.example.com/"],
 "background": {"service_worker": "bg.js"},

bg.js for Chrome 101 and newer using initiatorDomains and requestDomains
(don't forget to add "minimum_chrome_version": "101" in manifest.json)

const iframeHosts = [
 'example.com',
];
chrome.runtime.onInstalled.addListener(() => {
 const RULE = {
 id: 1,
 condition: {
 initiatorDomains: [chrome.runtime.id],
 requestDomains: iframeHosts,
 resourceTypes: ['sub_frame'],
 },
 action: {
 type: 'modifyHeaders',
 responseHeaders: [
 {header: 'X-Frame-Options', operation: 'remove'},
 {header: 'Frame-Options', operation: 'remove'},
 // Uncomment the following line to suppress `frame-ancestors` error
 // {header: 'Content-Security-Policy', operation: 'remove'},
 ],
 },
 };
 chrome.declarativeNetRequest.updateDynamicRules({
 removeRuleIds: [RULE.id],
 addRules: [RULE],
 });
});

Old Chrome 84-100

Use the following instead, if your extension should be compatible with these old versions.

manifest.json for Chrome 84 and newer,
shows a separate permission for "Block page content" during installation

 "permissions": ["declarativeNetRequest"],
 "host_permissions": ["*://*.example.com/"],
 "background": {"service_worker": "bg.js"},

bg.js for Chrome 84 and newer using the now deprecated domains

const iframeHosts = [
 'example.com',
];
chrome.runtime.onInstalled.addListener(() => {
 chrome.declarativeNetRequest.updateDynamicRules({
 removeRuleIds: iframeHosts.map((h, i) => i + 1),
 addRules: iframeHosts.map((h, i) => ({
 id: i + 1,
 condition: {
 domains: [chrome.runtime.id],
 urlFilter: `||${h}/`,
 resourceTypes: ['sub_frame'],
 },
 action: {
 type: 'modifyHeaders',
 responseHeaders: [
 {header: 'X-Frame-Options', operation: 'remove'},
 {header: 'Frame-Options', operation: 'remove'},
 // Uncomment the following line to suppress `frame-ancestors` error
 // {header: 'Content-Security-Policy', operation: 'remove'},
 ],
 },
 })),
 });
});

Warning: beware of site's service worker

You may have to remove the service worker of the site(s) before adding the iframe or before opening the extension page because many modern sites use the service worker to create the page without making a network request thus ignoring our header-stripping rule.

  1. Add "browsingData" to "permissions" in manifest.json

  2. Clear the SW:

    function removeSW(url) {
     return chrome.browsingData.remove({
     origins: [new URL(url).origin],
     }, {
     serviceWorkers: true,
     });
    }
    

    // If you add an iframe element in DOM:

    async function addIframe(url, parent = document.body) {
     await removeSW(url);
     const el = document.createElement('iframe');
     parent.appendChild(el);
     el.src = url;
     return el;
    }
    

    // If you open an extension page with an <iframe> element in its HTML:

    async function openPage(url) {
     await removeSW('https://example.com/');
     return chrome.tabs.create({url});
    }
    

In Firefox chrome.runtime.id is a different thing

Use chrome.runtime.getURL('').split('/')[2] instead.

answered Sep 14, 2021 at 12:28

5 Comments

We did everything except for the manifest host_permissions, and it failed. Thanks for the very complete solution! Also, for the lazy people who don't care about security, this is a valid URL pattern: "<all_urls>" o.o
Side note: For those of you whom are trying to use an extension to inject iframe from a website inside another website (NOT inside the extension itself), please note, you'll need to change the rule's initiatorDomains to be the domain of the hosting website, NOT the domain of the extension itself.
I'm looking to implement this in the popup of an extension. How do I access the iframe content once I've implemented this manifest and bg.js please? I can't see where the resultant iframe goes! Thanks.
Register a content script using chrome.scripting.registerContentScripts with allFrames:true. The content script will run inside the iframe and see its contents. It can use chrome.runtime.sendMessage (safe) or parent.postMessage (spoofable) to communicate to the popup directly. Note that the popup is a separate window so it has its own separate devtools: right-click inside the popup and select "inspect" in the menu.
I created MV3 extension, which does similar thing. Not only it changes headers, it also applies many other hacks to get real world sites working. See my blog post and source code here: safinaskar.writeas.com/…
2

You can try the Frame extension that lets the user drop X-Frame-Options and Content-Security-Policy HTTP response headers, allowing pages to be iframed.

The code is available on github

It's based on ManifestV3 and working perfectly with Google & Facebook.

answered Jun 1, 2022 at 8:40

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.