Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chrome/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Development
# Development

To use the development version:

Expand Down
20 changes: 15 additions & 5 deletions chrome/extension/inner-sandbox.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<base>
<script src="js/innersandbox.js"></script>
<body>

</body>
<meta charset="utf-8" />
<title>Retire.js Inner Sandbox</title>
<script>
(function () {
if (!document.querySelector("base")) {
const base = document.createElement("base");
base.href = "/";
document.head.appendChild(base);
}
})();
</script>
<script src="js/innersandbox.js" type="module"></script>
</head>
<body></body>
</html>
39 changes: 32 additions & 7 deletions chrome/extension/js/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,39 @@ const hasher = {
},
};

async function fetchScriptText(url) {
try {
const res = await fetch(url, { credentials: "omit", cache: "reload" });
if (!res.ok) {
console.debug("Fetch non-OK", url, res.status);
return null;
}
return await res.text();
} catch (err) {
console.debug("Fetch error", url, err);
return null;
}
}


async function download(url) {
const response = await fetch(url);
if (response.ok) {
return response.text();
} else {
throw new Error(
"Got " + response.status + " when trying to download " + url
);
try {
const response = await fetch(url);
if (response.ok) {
return await response.text();
} else {
// Suppress 403/404 silently; log others for debug
if (response.status !== 403 && response.status !== 404) {
console.warn(`Download failed with status ${response.status} for ${url}`);
}
return ''; // Empty on error to continue scanning
}
} catch (e) {
// Network errors (e.g., CORS, abort) – suppress unless critical
if (!e.message.includes('Failed to fetch') && !e.message.includes('network failure')) {
console.warn(`Download error: ${e.message} for ${url}`);
}
return '';
}
}

Expand Down
167 changes: 159 additions & 8 deletions chrome/extension/js/generated/retire-chrome.js

Large diffs are not rendered by default.

143 changes: 89 additions & 54 deletions chrome/extension/js/innersandbox.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,95 @@
var realwin = window;
var realdoc = document;
console.log("inner sandbox loaded");

window.addEventListener("message", function (evt) {
//console.log('inner', evt, evt.data);
if (!evt.data.script) return evt.source.postMessage({ done: "true" }, "*");
var repoFuncs = evt.data.repoFuncs;
console.log("I'm trying!!");
//try {
["alert", "prompt", "confirm"].forEach(function (n) {
try {
Object.defineProperty(window, n, {
get: function () {
return function () {};
},
set: function () {},
enumerable: true,
configurable: false,
});
} catch (e) {}
});
// Retire.js inner sandbox (SAFE ANALYZER)
// This version NEVER executes page scripts.
// It only runs regex/hash extractors on provided script text.

import { repo } from "./retire-chrome.js"; // built repo with extractors

(function () {
"use strict";
console.log("inner sandbox (analyzer) loaded");

// Utility: test filename, URI, filecontent, and hashes
function analyzeScript(data) {
const results = [];
const { url, content } = data;
if (!url && !content) return results;

for (const [lib, def] of Object.entries(repo)) {
const extractors = def.extractors || {};
let version = null;

// Filename match
if (!version && extractors.filename) {
extractors.filename.forEach((re) => {
const m = new RegExp(re).exec(url || "");
if (m && m[1]) version = m[1];
});
}

// URI match
if (!version && extractors.uri) {
extractors.uri.forEach((re) => {
const m = new RegExp(re).exec(url || "");
if (m && m[1]) version = m[1];
});
}

//Make sure other scripts are loaded correctly
if (evt.data.url) {
document
.getElementsByTagName("base")[0]
.setAttribute(
"href",
evt.data.url.replace(/(https?:\/\/[^\/]+).*/, "$1/")
);
// Filecontent match
if (!version && extractors.filecontent && content) {
extractors.filecontent.forEach((re) => {
const m = new RegExp(re).exec(content);
if (m && m[1]) version = m[1];
});
}

// Hash match
if (!version && extractors.hashes && content) {
// Compute sha1 of content
try {
const enc = new TextEncoder();
const buf = enc.encode(content);
crypto.subtle.digest("SHA-1", buf).then((hash) => {
const hex = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
if (extractors.hashes[hex]) {
version = extractors.hashes[hex];
postResult(lib, version, data);
}
});
} catch (e) {
console.debug("Hashing failed", e);
}
}

if (version) {
results.push({ lib, version });
postResult(lib, version, data);
}
}

return results;
}

//Anti framebusting
window.fun = new Function("top", evt.data.script);
try {
console.log("SANDBOX invoking", evt.data.url);
window.fun(window);
} catch (e) {
console.warn("SANDBOX ERROR", e);
function postResult(lib, version, original) {
window.parent.postMessage(
{ component: lib, version, original },
"*"
);
}
Object.entries(repoFuncs).forEach(([component, funcs]) => {
funcs.forEach(function (func) {

// Main message handler
window.addEventListener("message", (evt) => {
try {
const data = evt.data || {};
analyzeScript(data);
evt.source && evt.source.postMessage({ done: true }, "*");
} catch (err) {
console.warn("SANDBOX ERROR analyzer", err);
try {
var result = eval(func);
console.log("SANDBOX eval", component, result);
evt.source.postMessage(
{ component: component, version: result, original: evt.data },
"*"
);
} catch (e) {
//if (component == "nextjs") console.log("SANDBOX ERROR", e);
}
});
evt.source &&
evt.source.postMessage({ done: true, error: String(err) }, "*");
} catch {}
}
});
/*} catch(e) {
console.warn(e);
}*/
evt.source.postMessage({ done: "true" }, "*");
});
})();
61 changes: 39 additions & 22 deletions chrome/extension/js/sandbox.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
var extension = null;
window.addEventListener("message", function orig(evt) {
if (evt.data.repoFuncs) {
console.log("SANDBOX: I received a message", evt);
extension = evt.source;
var iframe = document.createElement("iframe");
iframe.retireEvent = evt;
iframe.src = "inner-sandbox.html";
iframe.setAttribute("data-url", evt.data.url);
//iframe.style = "visibility: hidden";
document.body.appendChild(iframe);
console.log("outer", evt.data);
setTimeout(function () {
iframe.contentWindow.postMessage(evt.data, "*");
}, 200);
setTimeout(function () {
iframe.remove();
}, 10000);
} else if (evt.data.version) {
extension.postMessage(evt.data, "*");
}
});
(function () {
"use strict";

var extension = null;

window.addEventListener("message", function (evt) {
try {
if (evt.data && evt.data.repoFuncs) {
console.log("SANDBOX: received scan request", evt.data.url);
extension = evt.source;

var iframe = document.createElement("iframe");
iframe.retireEvent = evt;
iframe.src = "inner-sandbox.html";

// True isolation; we don't need same-origin
iframe.setAttribute("sandbox", "allow-scripts");

iframe.setAttribute("data-url", evt.data.url);
iframe.style.display = "none";
document.body.appendChild(iframe);

setTimeout(function () {
try { iframe.contentWindow.postMessage(evt.data, "*"); }
catch (err) { console.warn("SANDBOX ERROR posting to iframe", err); }
}, 200);

setTimeout(function () {
try { iframe.remove(); }
catch (err) { console.warn("SANDBOX ERROR removing iframe", err); }
}, 10000);
} else if (evt.data && evt.data.version) {
extension && extension.postMessage(evt.data, "*");
}
} catch (err) {
console.warn("SANDBOX ERROR outer handler", err);
}
});
})();
17 changes: 16 additions & 1 deletion chrome/extension/js/service_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ var scanEnabled = true;
var deepScanEnabled = true;
var repo;

async function fetchScriptText(url) {
try {
const res = await fetch(url, { credentials: "omit", cache: "reload" });
if (!res.ok) {
console.debug("Fetch non-OK", url, res.status);
return null;
}
return await res.text();
} catch (err) {
console.debug("Fetch error", url, err);
return null;
}
}


async function createOffscreen() {
if (await chrome.offscreen.hasDocument()) return;
chrome.offscreen.createDocument({
Expand Down Expand Up @@ -151,4 +166,4 @@ function showResult(result, details) {
}
return true;
//}, 3000);
}
}
2 changes: 1 addition & 1 deletion chrome/extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"matches": ["<all_urls>"],
"js": ["js/content.js"],
"run_at": "document_start"
}
Expand Down
2 changes: 1 addition & 1 deletion chrome/extension/sandbox.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<script src="js/sandbox.js"></script>

</head>
<body>
<h2><img src="icons/icon48.png" style="vertical-align: middle"> Retire.js</h2>
<div>This window is a sandbox for JavaScript version detection. Please ignore...</div>
Expand Down
Loading