Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #337 +/- ##
==========================================
- Coverage 43.66% 41.53% -2.14%
==========================================
Files 31 33 +2
Lines 4967 5051 +84
==========================================
- Hits 2169 2098 -71
- Misses 2798 2953 +155 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…o mar10/issue336
…o mar10/issue336
Co-authored-by: mar10 <41663+mar10@users.noreply.github.com> Agent-Logs-Url: https://github.com/mar10/wsgidav/sessions/f1fe7d0e-81a9-4633-b7e3-4cf77a354bb5
Add push and pull_request triggers for master/main and branch patterns (release/**, hotfix/**) and expand docker/metadata-action tagging to include latest, semver, branch, PR and commit-sha tags. Add OCI labels for the image. Make docker build/push conditional so only releases, the default branch, or manual runs push to the registry (PRs/feature branches only build). Restrict artifact attestation to runs where the push succeeded and a digest was produced.
| // to prevent XSS | ||
| placeholderElem.innerHTML = `File is too large (> ${maxSizeKB} KiB).<br> | ||
| <a href="" target="_blank">Click here</a> to preview.`; | ||
| placeholderElem.querySelector("a").href = url; |
Check failure
Code scanning / CodeQL
DOM text reinterpreted as HTML High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 14 days ago
The fix is to validate and constrain any untrusted URL before writing it to DOM URL sinks (href, src, iframe src).
Best approach here: add a small helper in previews.js that parses URLs with new URL(candidate, window.location.href) and only allows safe protocols (http: and https:). If invalid/unsafe, fall back to about:blank (or skip preview).
In wsgidav/mw/dav_explorer/htdocs/previews.js:
- Add a helper function (near other utilities) to sanitize preview URLs.
- In
showPreview, create asafeUrlfromurl. - Replace assignments at:
- line 313 (
a.href = url) →a.href = safeUrl - line 348 (
iframeElem.setAttribute("src", url)) →safeUrl - and image assignment in line 335 should also use
safeUrlfor consistency.
- line 313 (
No external dependency is required.
| @@ -273,6 +273,18 @@ | ||
| } | ||
| } | ||
|
|
||
| function sanitizePreviewUrl(url) { | ||
| try { | ||
| const parsed = new URL(url, window.location.href); | ||
| if (parsed.protocol === "http:" || parsed.protocol === "https:") { | ||
| return parsed.href; | ||
| } | ||
| } catch (_err) { | ||
| // Invalid URL | ||
| } | ||
| return "about:blank"; | ||
| } | ||
|
|
||
| export async function showPreview(urlOrNode, options = {}) { | ||
| let { autoOpen = false, iframe = false, maxSizeKB = settingsStore.get("max_preview_size_kb") } = options; | ||
|
|
||
| @@ -290,6 +302,7 @@ | ||
| } | ||
| const node = (!urlOrNode || typeof urlOrNode === "string") ? null : urlOrNode; | ||
| const url = node ? getNodeResourceUrl(urlOrNode) : urlOrNode; | ||
| const safeUrl = sanitizePreviewUrl(url); | ||
| const isFolder = node?.type === "directory"; | ||
| let preview = null; | ||
|
|
||
| @@ -310,7 +323,7 @@ | ||
| // to prevent XSS | ||
| placeholderElem.innerHTML = `File is too large (> ${maxSizeKB} KiB).<br> | ||
| <a href="" target="_blank">Click here</a> to preview.`; | ||
| placeholderElem.querySelector("a").href = url; | ||
| placeholderElem.querySelector("a").href = safeUrl; | ||
| preview = null; | ||
| } | ||
|
|
||
| @@ -332,11 +345,11 @@ | ||
| case "image": | ||
| imgElem.onload = () => { | ||
| imgElem.onload = null; | ||
| imgElem.setAttribute("src", url); | ||
| imgElem.setAttribute("src", safeUrl); | ||
| }; | ||
| imgElem.onerror = (e) => { | ||
| imgElem.onerror = null; | ||
| console.warn("Error loading preview %s", url, e); | ||
| console.warn("Error loading preview %s", safeUrl, e); | ||
| imgElem.src = imgPlaceholderErrorSvg; | ||
| }; | ||
| imgElem.setAttribute("src", imgPlaceholderLoadingSvg); | ||
| @@ -345,7 +355,7 @@ | ||
| // const iframe = document.querySelector("aside.right div#preview-iframe iframe"); | ||
| // const iframe = iframeElem.querySelector("iframe"); | ||
| iframeElem.src = "about:blank"; // Reset before setting new src | ||
| iframeElem.setAttribute("src", url); | ||
| iframeElem.setAttribute("src", safeUrl); | ||
| break; | ||
| } | ||
|
|
| case "image": | ||
| imgElem.onload = () => { | ||
| imgElem.onload = null; | ||
| imgElem.setAttribute("src", url); |
Check failure
Code scanning / CodeQL
DOM text reinterpreted as HTML High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 14 days ago
General fix: validate and constrain any URL derived from potentially tainted sources before assigning it to DOM URL attributes (src, href). Only allow safe protocols (http:, https:) and optionally same-origin for preview contexts.
Best fix here (without changing intended behavior): in showPreview (wsgidav/mw/dav_explorer/htdocs/previews.js), normalize url via new URL(url, window.location.href), reject invalid or non-HTTP(S) schemes, and abort preview safely if invalid. Use the sanitized URL string for all downstream uses (fetch, img.src, iframe.src, link href). This contains taint at one choke point and preserves existing UX for valid URLs.
| @@ -289,7 +289,27 @@ | ||
| } | ||
| } | ||
| const node = (!urlOrNode || typeof urlOrNode === "string") ? null : urlOrNode; | ||
| const url = node ? getNodeResourceUrl(urlOrNode) : urlOrNode; | ||
| const rawUrl = node ? getNodeResourceUrl(urlOrNode) : urlOrNode; | ||
| let url; | ||
| try { | ||
| const parsedUrl = new URL(rawUrl, window.location.href); | ||
| if (!["http:", "https:"].includes(parsedUrl.protocol)) { | ||
| throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol}`); | ||
| } | ||
| url = parsedUrl.toString(); | ||
| } catch (e) { | ||
| console.warn("Blocked unsafe preview URL:", rawUrl, e); | ||
| placeholderElem.textContent = "No preview available."; | ||
| placeholderElem.classList.remove("hidden"); | ||
| imgElem.classList.add("hidden"); | ||
| textElem.classList.add("hidden"); | ||
| settingsElem.classList.add("hidden"); | ||
| folderElem.classList.add("hidden"); | ||
| iframeElem.classList.add("hidden"); | ||
| nodeInfo.classList.toggle("hidden", !node); | ||
| updateNodePreviewInfo(node); | ||
| return false; | ||
| } | ||
| const isFolder = node?.type === "directory"; | ||
| let preview = null; | ||
|
|
| // const iframe = document.querySelector("aside.right div#preview-iframe iframe"); | ||
| // const iframe = iframeElem.querySelector("iframe"); | ||
| iframeElem.src = "about:blank"; // Reset before setting new src | ||
| iframeElem.setAttribute("src", url); |
Check failure
Code scanning / CodeQL
DOM text reinterpreted as HTML High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 14 days ago
The best fix is to validate and normalize iframe URLs before assignment, allowing only safe protocols (http: / https:), and reject everything else. This preserves current behavior for normal help/document links while preventing scriptable or ambiguous URL schemes from being interpreted by the browser.
Make the change in wsgidav/mw/dav_explorer/htdocs/previews.js inside showPreview:
- In the
"iframe"case, replace direct assignment (setAttribute("src", url)) with:- Parse via
new URL(url, window.location.href)(supports relative URLs safely). - Check protocol whitelist (
http:andhttps:). - If invalid, keep iframe blank and show a warning/placeholder message.
- If valid, assign
iframeElem.src = parsed.href.
- Parse via
No new imports or dependencies are required.
| @@ -345,7 +345,20 @@ | ||
| // const iframe = document.querySelector("aside.right div#preview-iframe iframe"); | ||
| // const iframe = iframeElem.querySelector("iframe"); | ||
| iframeElem.src = "about:blank"; // Reset before setting new src | ||
| iframeElem.setAttribute("src", url); | ||
| try { | ||
| const iframeUrl = new URL(url, window.location.href); | ||
| if (iframeUrl.protocol === "http:" || iframeUrl.protocol === "https:") { | ||
| iframeElem.src = iframeUrl.href; | ||
| } else { | ||
| console.warn("Blocked unsafe iframe URL protocol:", iframeUrl.protocol, url); | ||
| placeholderElem.textContent = "Blocked unsafe preview URL."; | ||
| placeholderElem.classList.remove("hidden"); | ||
| } | ||
| } catch (e) { | ||
| console.warn("Invalid iframe preview URL:", url, e); | ||
| placeholderElem.textContent = "Invalid preview URL."; | ||
| placeholderElem.classList.remove("hidden"); | ||
| } | ||
| break; | ||
| } | ||
|
|
No description provided.