Skip to content

Commit eb5ae00

Browse files
committed
Misc fixes in assets JS
1 parent e09e8cc commit eb5ae00

10 files changed

Lines changed: 55 additions & 23 deletions

assets/js/app.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require('../css/app.css');
44
import bootstrap from 'bootstrap/dist/js/bootstrap.bundle';
55
import Mark from 'mark.js/src/vanilla';
66
import Autocomplete from './autocomplete';
7-
import { toggleVisibilityClasses } from './helpers';
7+
import { sanitizeUrl, toggleVisibilityClasses } from './helpers';
88

99
// Provide Bootstrap variable globally to allow custom backend pages to use it
1010
window.bootstrap = bootstrap;
@@ -198,8 +198,15 @@ class App {
198198

199199
const filterModal = document.querySelector(filterButton.getAttribute('data-bs-target'));
200200

201+
// the filter URL is fetched and its response is injected into the page (see below),
202+
// so it must be same-origin to prevent loading attacker-controlled remote HTML
203+
const filtersUrl = sanitizeUrl(filterButton.getAttribute('data-href'), true);
204+
if (null === filtersUrl) {
205+
return;
206+
}
207+
201208
// this is needed to avoid errors when connection is slow
202-
filterButton.setAttribute('href', filterButton.getAttribute('data-href'));
209+
filterButton.setAttribute('href', filtersUrl);
203210
filterButton.removeAttribute('data-href');
204211
filterButton.classList.remove('disabled');
205212

@@ -665,7 +672,10 @@ class App {
665672
return;
666673
}
667674
event.preventDefault();
668-
window.location = element.getAttribute('data-ea-action-url');
675+
const actionUrl = sanitizeUrl(element.getAttribute('data-ea-action-url'));
676+
if (null !== actionUrl) {
677+
window.location = actionUrl;
678+
}
669679
});
670680
});
671681
}

assets/js/field-file-upload.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class FileUploadField {
3838
totalSizeInBytes += file.size;
3939
}
4040

41-
this.#getFieldCustomInput().innerHTML = filename;
41+
this.#getFieldCustomInput().textContent = filename;
4242
this.#getFieldDeleteButton().style.display = 'block';
4343
this.#getFieldSizeLabel().childNodes.forEach((fileUploadFileSizeLabelChild) => {
4444
if (fileUploadFileSizeLabelChild.nodeType === Node.TEXT_NODE) {
@@ -57,7 +57,7 @@ class FileUploadField {
5757
fieldDeleteCheckbox.click();
5858
}
5959
this.field.value = '';
60-
this.#getFieldCustomInput().innerHTML = '';
60+
this.#getFieldCustomInput().textContent = '';
6161
toggleVisibilityClasses(this.#getFieldDeleteButton(), true);
6262

6363
this.#getFieldSizeLabel().childNodes.forEach((fileSizeLabelChild) => {

assets/js/field-slug.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,8 @@ class Slugger {
8686
this.lockButton.addEventListener('click', () => {
8787
if (this.locked) {
8888
const confirmMessage = this.field.dataset.confirmText || null;
89-
if (null === confirmMessage) {
89+
if (null === confirmMessage || true === confirm(confirmMessage)) {
9090
this.unlock();
91-
} else {
92-
const formattedConfirmMessage = decodeURIComponent(
93-
JSON.parse(`"${confirmMessage.replace(/\"/g, '\\"')}"`)
94-
);
95-
if (true === confirm(formattedConfirmMessage)) {
96-
this.unlock();
97-
}
9891
}
9992
} else {
10093
this.lock();

assets/js/helpers.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,32 @@ export function toggleVisibilityClasses(element, removeVisibility) {
77
element.classList.add('d-block');
88
}
99
}
10+
11+
// Validates a URL read from the DOM before using it to navigate or fetch. It only
12+
// allows safe schemes and rejects dangerous ones (e.g. "javascript:" or "data:") to
13+
// prevent DOM-based XSS. When requireSameOrigin is true, cross-origin URLs are also
14+
// rejected; this is required for flows that fetch a URL and inject the response into
15+
// the page, so that attacker-controlled remote content can never be loaded. Returns
16+
// the original URL when it's safe or null otherwise.
17+
export function sanitizeUrl(url, requireSameOrigin = false) {
18+
if (null === url || '' === url) {
19+
return null;
20+
}
21+
22+
try {
23+
// relative URLs are resolved against the current origin; absolute URLs keep their own scheme
24+
const parsedUrl = new URL(url, window.location.origin);
25+
const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
26+
if (!allowedProtocols.includes(parsedUrl.protocol)) {
27+
return null;
28+
}
29+
30+
if (requireSameOrigin && parsedUrl.origin !== window.location.origin) {
31+
return null;
32+
}
33+
34+
return url;
35+
} catch {
36+
return null;
37+
}
38+
}
Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/entrypoints.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"/app.9c5ece20.css"
66
],
77
"js": [
8-
"/app.70d109e6.js"
8+
"/app.33e2ad73.js"
99
]
1010
},
1111
"form": {
@@ -43,7 +43,7 @@
4343
},
4444
"field-file-upload": {
4545
"js": [
46-
"/field-file-upload.35a9e188.js"
46+
"/field-file-upload.fc04f096.js"
4747
]
4848
},
4949
"field-image": {
@@ -53,7 +53,7 @@
5353
},
5454
"field-slug": {
5555
"js": [
56-
"/field-slug.037a5045.js"
56+
"/field-slug.bf0f4845.js"
5757
]
5858
},
5959
"field-textarea": {
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)