Skip to content

Instantly share code, notes, and snippets.

@sirdarckcat
Last active March 4, 2024 05:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sirdarckcat/15cf8f924389e9535de16c2e6d952075 to your computer and use it in GitHub Desktop.
Save sirdarckcat/15cf8f924389e9535de16c2e6d952075 to your computer and use it in GitHub Desktop.
Isolated Scripts
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var secrets = {};
// 1. If response header "Isolated-Script" is "true" and type is "script"
// 1.1. Cancel request, and inject response as content script
chrome.webRequest.onHeadersReceived.addListener(
function(info) {
var scriptUrl = new URL(info.url);
if ((info.responseHeaders||[]).find(
pair=>pair.name.match(/^Isolated-Script$/i) && pair.value=="true")
) {
fetch(scriptUrl.href, {redirect: "error", credentials: "include"})
.then(response=>
response.status < 300
&& response.headers.get('content-type').match(/^(text|application)\/javascript(;.*)?$/)
? response.text()
: 'console.error("bad script response")')
.then(code=>chrome.tabs.executeScript(info.tabId, {
code: wrapCode(
code,
secrets[scriptUrl.origin] = String(secrets[scriptUrl.origin] || Math.random())),
frameId: info.frameId
}));
chrome.tabs.executeScript(info.tabId, {
file: "secrethtml.js",
frameId: info.frameId
});
return {redirectUrl: 'data:text/javascript,console.log("loading script as isolate");'};
}
},
// filters
{
urls: ["*://*/*"],
types: ["script"]
},
// extraInfoSpec
["blocking", "responseHeaders"]);
// 1.2. Replace fetch and XMLHttpRequest.send to add "Isolated-Script-Secret: SECRET" to every request made.
function wrapCode(code, secret) {
var wrapper = function(self, secret, code){
console.log('running isolate script');
self.fetch = new Proxy(self.fetch, {
apply: function(fetch, window, args) {
args[1] = args[1] || {};
args[1].headers = args[1].headers || {};
args[1].headers["Isolated-Script-Secret"] = secret;
return fetch.apply(window, args);
}});
self.XMLHttpRequest.prototype.send = new Proxy(self.XMLHttpRequest.prototype.send, {
apply: function(send, xhr, args) {
xhr.addRequestHeader("Isolated-Script-Secret", secret);
return xhr.send.apply(xhr, args);
}
});
code();
};
var code = "function(){" + code + "}";
return "(" + wrapper + ")(self," + JSON.stringify(secret) + "," + code + ")";
}
// 2. If request header "Isolated-Script-Secret" exists
// 2.1 Remove from every request, but remember it's value.
// 3. If request header "Cookie" contains cookie prefixed "__isolatedScript-" and request header "Isolated-Script-Secret" didn't contain secret.
// 3.1. Remove cookie from request.
chrome.webRequest.onBeforeSendHeaders.addListener(
function(info){
var scriptUrl = new URL(info.url);
var requestHeaders = [];
var claimedSecret, cookieIndex, dirtyCookies, cleanCookies, cookies;
(info.requestHeaders||[]).forEach(function(header, index){
if (header.name.match(/^Isolated-Script-Secret$/i)) {
claimedSecret = header.value;
} else
if (header.name.match(/^Cookie$/i)) {
cookieIndex = index;
dirtyCookies = header.value.split(';');
cleanCookies = dirtyCookies.filter(function(cookie) {
return !cookie.match(/^\s*__isolatedScript-/);
});
} else {
requestHeaders.push(header);
}
});
if (dirtyCookies) {
if (claimedSecret && claimedSecret == secrets[scriptUrl.origin]) {
cookies = dirtyCookies.join(';');
} else {
cookies = cleanCookies.join(';');
}
requestHeaders.push({name: "Cookie", value: cookies});
}
return {requestHeaders: requestHeaders};
},
// filters
{
urls: ["*://*/*"]
},
// extraInfoSpec
["blocking", "requestHeaders"]);
{
"name": "Isolate Scripts Demo",
"version": "0.1.1",
"description": "Demo for Isolate Scripts",
"permissions": [
"webRequest",
"webRequestBlocking",
"https://isolate-scripts-demo.appspot.com/*"
],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var bootstrap = function(self) {
self.onmessage = function(m) {
document.open();
document.write(m.data.html);
document.close();
var eventPort = m.ports[0];
for(var x in document.body) {
if (x.match(/^on/)) {
document.body[x] = function(event) {
var clone = {};
for (var n in event) {
try {
var serialized = JSON.stringify(event[n]);
if (serialized == "{}" || serialized == "[]" || serialized == "null") {
continue;
}
clone[n] = JSON.parse(serialized);
} catch (e) {}
};
eventPort.postMessage(clone);
};
}
}
document.documentElement.style.cssText = "padding: 0; margin: 0; position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%: width: 100%;";
document.body.style.cssText = m.data.style;
};
}
Object.defineProperty(Element.prototype, 'innerHTML', {
set: function(html){
if (html) {
var element = this;
var style = window.getComputedStyle(this, null).cssText;
var shadow = this.attachShadow({mode: 'closed'});
var iframe = document.createElement('iframe');
iframe.srcdoc = "<script>(" + bootstrap + ")(self)</script>";
iframe.sandbox = "allow-forms allow-scripts";
iframe.style.cssText = "height: 100%; width: 100%; padding: 0; margin: 0; border: 0;";
var mc = new MessageChannel();
mc.port1.onmessage = function(e) {
// Fake the event
var fakeEvent;
if (!fakeEvent && e.data.type.match(/^mouse|click/)) {
try {
fakeEvent = new MouseEvent(e.data.type, e.data);
} catch(e) {}
}
if (!fakeEvent) {
try {
fakeEvent = new UIEvent(e.data.type, e.data);
} catch(e) {}
}
if (!fakeEvent) {
try {
fakeEvent = new Event(e.data.type, e.data);
} catch(e) {}
}
if (!fakeEvent) {
try {
fakeEvent = new CustomEvent(e.data.type, e.data);
} catch(e) {}
}
if (fakeEvent) {
element.dispatchEvent(fakeEvent);
}
};
iframe.onload = function() {
iframe.onload = null;
iframe.contentWindow.postMessage({
html: html,
style: style
}, '*', [mc.port2]);
};
shadow.appendChild(iframe);
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment