Skip to content

Commit f78682b

Browse files
committed
Secutiry update
1 parent b0e3a9c commit f78682b

File tree

7 files changed

+190
-17
lines changed

7 files changed

+190
-17
lines changed

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@ node build.js
5353
* [ ] `GM_confirm`
5454
* [ ] Additional APIs as needed
5555

56-
### Security & Permissions
57-
58-
* [ ] Add new security settings
59-
* [ ] Allow scripts to load external resources
60-
* [ ] Ask for confirmation when a script runs on a website for the first time
61-
6256
### Editor
6357

6458
* [ ] Reduce editor bundle size (~1.4 MB currently)

src/GM/gm_core.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,12 @@
546546
});
547547
}
548548
// NEEDS HELP!
549-
_handleCrossOriginXmlhttpRequest(details) {
549+
async _handleCrossOriginXmlhttpRequest(details) {
550+
const settings = await this.bridge.call("getSettings");
551+
if (!settings.allowExternalResources) {
552+
throw new Error("GM_xmlhttpRequest: Cross-origin requests are disabled by a security setting.");
553+
}
554+
550555
const callbacks = {};
551556
const cloneableDetails = {};
552557

@@ -559,7 +564,17 @@
559564
});
560565

561566
return this.bridge.call("xmlhttpRequest", { details: cloneableDetails })
562-
.then((response) => {
567+
.then(async (response) => {
568+
if (response && cloneableDetails.responseType === 'blob' && response.response && typeof response.response === 'string' && response.response.startsWith('data:')) {
569+
try {
570+
const res = await fetch(response.response);
571+
const blob = await res.blob();
572+
response.response = blob;
573+
} catch (e) {
574+
console.error("CodeTweak: Failed to reconstruct blob from data URL.", e);
575+
}
576+
}
577+
563578
if (callbacks.onload) {
564579
try {
565580
callbacks.onload(response);
@@ -596,7 +611,6 @@
596611
}
597612
}
598613

599-
// I feel like we could move this to its own independent file
600614
async function executeUserScriptWithDependencies(userCode, scriptId, requireUrls, loader) {
601615
// Set up error collection
602616
const errorHandler = (event) => {

src/background/background.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ async function handleGreasyForkInstall(url) {
194194
}
195195

196196
async function handleCrossOriginXmlhttpRequest(details, tabId, sendResponse) {
197+
const { settings = {} } = await chrome.storage.local.get("settings");
198+
if (!settings.allowExternalResources) {
199+
sendResponse({ error: "Cross-origin requests are disabled by a security setting." });
200+
return;
201+
}
202+
197203
const { url, method = "GET", headers, data } = details;
198204

199205
if (!url) {
@@ -333,7 +339,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
333339
}
334340
const { action, ...payload } = message.payload;
335341

336-
if (action === "xmlhttpRequest") {
342+
if (action === "getSettings") {
343+
chrome.storage.local.get("settings").then(({ settings = {} }) => {
344+
sendResponse({ result: settings });
345+
});
346+
return true;
347+
} else if (action === "xmlhttpRequest") {
337348
// Still not working properly....
338349
handleCrossOriginXmlhttpRequest(payload.details, sender.tab.id, sendResponse);
339350
return true;

src/dashboard/dashboard-logic.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ async function loadSettings(settingsElements) {
118118
enableAllScripts: true,
119119
showNotifications: true,
120120
confirmBeforeRunning: false,
121-
121+
allowExternalResources: true,
122+
confirmFirstRun: false,
122123
};
123124

124125
// Apply defaults and update storage if needed

src/dashboard/dashboard.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,23 @@ <h3>General</h3>
116116
</span>
117117
</label>
118118
</div>
119+
<div class="settings-group">
120+
<h3>Security</h3>
121+
<label class="setting-item">
122+
<input type="checkbox" id="allowExternalResources">
123+
<span class="setting-label">
124+
<strong>Allow external resources</strong>
125+
<small>Allow scripts to load resources from external URLs</small>
126+
</span>
127+
</label>
128+
<label class="setting-item">
129+
<input type="checkbox" id="confirmFirstRun">
130+
<span class="setting-label">
131+
<strong>Confirm first run</strong>
132+
<small>Ask for confirmation before a script runs on a website for the first time</small>
133+
</span>
134+
</label>
135+
</div>
119136
<div class="settings-actions">
120137
<button type="submit" class="btn-primary">Save Settings</button>
121138
<button type="reset" class="btn-secondary">Reset Defaults</button>

src/dashboard/dashboard.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ function initDashboard() {
1616
scriptsTable: document.getElementById("scriptsTable"),
1717
scriptsList: document.getElementById("scriptsList"),
1818
emptyState: document.getElementById("emptyState"),
19+
settingsForm: document.getElementById("settingsForm"),
1920
saveSettingsBtn: document.getElementById("saveSettingsBtn"),
2021
resetSettingsBtn: document.getElementById("resetSettingsBtn"),
2122
exportAllBtn: document.getElementById("exportAllBtn"),
@@ -32,6 +33,8 @@ function initDashboard() {
3233
enableAllScripts: document.getElementById("enableAllScripts"),
3334
showNotifications: document.getElementById("showNotifications"),
3435
darkMode: document.getElementById("darkMode"),
36+
allowExternalResources: document.getElementById("allowExternalResources"),
37+
confirmFirstRun: document.getElementById("confirmFirstRun"),
3538
},
3639
greasyfork: {
3740
button: document.getElementById("greasyforkBtn"),
@@ -76,9 +79,10 @@ function setupEventListeners(elements, state) {
7679
window.location.href = chrome.runtime.getURL("editor/editor.html");
7780
});
7881

79-
elements.saveSettingsBtn?.addEventListener("click", () =>
80-
saveSettings(elements.settings)
81-
);
82+
elements.settingsForm?.addEventListener("submit", (e) => {
83+
e.preventDefault();
84+
saveSettings(elements.settings);
85+
});
8286

8387
elements.resetSettingsBtn?.addEventListener("click", () => {
8488
showNotification("Settings reset to defaults", "success");

src/utils/inject.js

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,144 @@ class ScriptInjector {
388388
const newScripts = matchingScripts.filter(
389389
(script) => !tabScripts.has(script.id)
390390
);
391-
for (const script of newScripts) {
392-
tabScripts.add(script.id);
393-
await this.injectScript(tabId, script, settings);
391+
392+
if (settings.confirmFirstRun) {
393+
const { scriptPermissions = {} } = await chrome.storage.local.get(
394+
"scriptPermissions"
395+
);
396+
const domain = new URL(url).hostname;
397+
398+
for (const script of newScripts) {
399+
const allowedDomains = scriptPermissions[script.id] || [];
400+
if (allowedDomains.includes(domain)) {
401+
tabScripts.add(script.id);
402+
await this.injectScript(tabId, script, settings);
403+
} else {
404+
const confirmed = await this.askForConfirmation(
405+
tabId,
406+
script.name,
407+
domain
408+
);
409+
if (confirmed) {
410+
allowedDomains.push(domain);
411+
scriptPermissions[script.id] = allowedDomains;
412+
await chrome.storage.local.set({ scriptPermissions });
413+
tabScripts.add(script.id);
414+
await this.injectScript(tabId, script, settings);
415+
}
416+
}
417+
}
418+
} else {
419+
for (const script of newScripts) {
420+
tabScripts.add(script.id);
421+
await this.injectScript(tabId, script, settings);
422+
}
394423
}
395424
}
396425

426+
async askForConfirmation(tabId, scriptName, domain) {
427+
const results = await chrome.scripting.executeScript({
428+
target: { tabId },
429+
world: "MAIN",
430+
func: (scriptName, domain) => {
431+
return new Promise((resolve) => {
432+
const container = document.createElement("div");
433+
container.id = "codetweak-confirm-container";
434+
435+
const shadow = container.attachShadow({ mode: "open" });
436+
437+
const style = document.createElement("style");
438+
style.textContent = `
439+
:host {
440+
position: fixed;
441+
top: 0;
442+
left: 0;
443+
width: 100%;
444+
height: 100%;
445+
background-color: rgba(0, 0, 0, 0.5);
446+
z-index: 2147483647;
447+
display: flex;
448+
justify-content: center;
449+
align-items: center;
450+
font-family: sans-serif;
451+
}
452+
.modal {
453+
background: white;
454+
padding: 20px;
455+
border-radius: 8px;
456+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
457+
width: 400px;
458+
max-width: 90%;
459+
}
460+
h3 {
461+
margin-top: 0;
462+
color: #333;
463+
}
464+
p {
465+
color: #555;
466+
}
467+
.buttons {
468+
text-align: right;
469+
margin-top: 20px;
470+
}
471+
button {
472+
padding: 10px 20px;
473+
border: none;
474+
border-radius: 5px;
475+
cursor: pointer;
476+
margin-left: 10px;
477+
}
478+
#allow-btn {
479+
background-color: #28a745;
480+
color: white;
481+
}
482+
#deny-btn {
483+
background-color: #dc3545;
484+
color: white;
485+
}
486+
`;
487+
488+
const modal = document.createElement("div");
489+
modal.className = "modal";
490+
modal.innerHTML = `
491+
<h3>CodeTweak Script Confirmation</h3>
492+
<p>Allow "<strong></strong>" to run on <strong></strong>?</p>
493+
<div class="buttons">
494+
<button id="deny-btn">Deny</button>
495+
<button id="allow-btn">Allow</button>
496+
</div>
497+
`;
498+
const strongs = modal.querySelectorAll("strong");
499+
strongs[0].textContent = scriptName;
500+
strongs[1].textContent = domain;
501+
502+
shadow.appendChild(style);
503+
shadow.appendChild(modal);
504+
505+
document.body.appendChild(container);
506+
507+
const cleanup = () => {
508+
if (container.parentNode) {
509+
container.parentNode.removeChild(container);
510+
}
511+
};
512+
513+
shadow.getElementById("allow-btn").addEventListener("click", () => {
514+
cleanup();
515+
resolve(true);
516+
});
517+
518+
shadow.getElementById("deny-btn").addEventListener("click", () => {
519+
cleanup();
520+
resolve(false);
521+
});
522+
});
523+
},
524+
args: [scriptName, domain],
525+
});
526+
return results[0].result;
527+
}
528+
397529
showNotification(tabId, scriptName) {
398530
chrome.scripting
399531
.executeScript({

0 commit comments

Comments
 (0)