fix(security): mitigate CodeQL alerts by adding SRI attributes and sanitizing DOM content

This commit is contained in:
Ryan
2025-04-07 23:15:06 -04:00
committed by GitHub
parent 6ff25ed426
commit 9701747214
4 changed files with 131 additions and 80 deletions

View File

@@ -23,11 +23,12 @@
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/material-darker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/resumable.js/1.1.0/resumable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js" integrity="sha384-UXbkZAbZYZ/KCAslc6UO4d6UHNKsOxZ/sqROSQaPTZCuEIKhfbhmffQ64uXFOcma" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js" integrity="sha384-xPpkMo5nDgD98fIcuRVYhxkZV6/9Y4L8s3p0J5c4MxgJkyKJ8BJr+xfRkq7kn6Tw" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js" integrity="sha384-to8njsu2GAiXQnY/aLGzz0DIY/SFSeSDodtvSl869n2NmsBdHOTZNNqbEBPYh7Pa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js" integrity="sha384-kmQrbJf09Uo1WRLMDVGoVG3nM6F48frIhcj7f3FDUjeRzsiHwyBWDjMUIttnIeAf" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/resumable.js/1.1.0/resumable.min.js" integrity="sha384-EXTg7rRfdTPZWoKVCslusAAev2TYw76fm+Wox718iEtFQ+gdAdAc5Z/ndLHSo4mq" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.4.0/purify.min.js" integrity="sha384-Tsl3d5pUAO7a13enIvSsL3O0/95nsthPJiPto5NtLuY8w3+LbZOpr3Fl2MNmrh1E" crossorigin="anonymous"></script>
<link rel="stylesheet" href="css/styles.css" />
</head>

View File

@@ -1,5 +1,5 @@
// editor.js
import { showToast } from './domUtils.js';
import { escapeHTML, showToast } from './domUtils.js';
import { loadFileList } from './fileListView.js';
function getModeForFile(fileName) {
@@ -73,14 +73,14 @@ export function editFile(fileName, folder) {
modal.classList.add("modal", "editor-modal");
modal.innerHTML = `
<div class="editor-header">
<h3 class="editor-title">Editing: ${fileName}</h3>
<h3 class="editor-title">Editing: ${escapeHTML(fileName)}</h3>
<div class="editor-controls">
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
</div>
<button id="closeEditorX" class="editor-close-btn">&times;</button>
</div>
<textarea id="fileEditor" class="editor-textarea">${content}</textarea>
<textarea id="fileEditor" class="editor-textarea">${escapeHTML(content)}</textarea>
<div class="editor-footer">
<button id="saveBtn" class="btn btn-primary">Save</button>
<button id="closeBtn" class="btn btn-secondary">Close</button>

View File

@@ -237,15 +237,27 @@ export function previewFile(fileUrl, fileName) {
// Added to preserve the original functionality.
export function displayFilePreview(file, container) {
const actualFile = file.file || file;
// Validate that actualFile is indeed a File
if (!(actualFile instanceof File)) {
console.error("displayFilePreview called with an invalid file object");
return;
}
container.style.display = "inline-block";
// Clear the container safely without using innerHTML
while (container.firstChild) {
container.removeChild(container.firstChild);
}
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(actualFile.name)) {
const img = document.createElement("img");
// Set the image source using a Blob URL (this is considered safe)
img.src = URL.createObjectURL(actualFile);
img.classList.add("file-preview-img");
container.innerHTML = "";
container.appendChild(img);
} else {
container.innerHTML = "";
const iconSpan = document.createElement("span");
iconSpan.classList.add("material-icons", "file-icon");
iconSpan.textContent = "insert_drive_file";

View File

@@ -58,9 +58,7 @@ function getParentFolder(folder) {
return lastSlash === -1 ? "root" : folder.substring(0, lastSlash);
}
/* ----------------------
Breadcrumb Functions
----------------------*/
function renderBreadcrumb(normalizedFolder) {
if (!normalizedFolder || normalizedFolder === "") return "";
const parts = normalizedFolder.split("/");
@@ -76,73 +74,113 @@ function renderBreadcrumb(normalizedFolder) {
return breadcrumbItems.join('');
}
function bindBreadcrumbEvents() {
const breadcrumbLinks = document.querySelectorAll(".breadcrumb-link");
breadcrumbLinks.forEach(link => {
link.addEventListener("click", function (e) {
e.stopPropagation();
let folder = this.getAttribute("data-folder");
window.currentFolder = folder;
localStorage.setItem("lastOpenedFolder", folder);
const titleEl = document.getElementById("fileListTitle");
titleEl.innerHTML = "Files in (" + renderBreadcrumb(folder) + ")";
expandTreePath(folder);
document.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
const targetOption = document.querySelector(`.folder-option[data-folder="${folder}"]`);
if (targetOption) targetOption.classList.add("selected");
loadFileList(folder);
bindBreadcrumbEvents();
});
link.addEventListener("dragover", function (e) {
e.preventDefault();
this.classList.add("drop-hover");
});
link.addEventListener("dragleave", function (e) {
this.classList.remove("drop-hover");
});
link.addEventListener("drop", function (e) {
e.preventDefault();
this.classList.remove("drop-hover");
const dropFolder = this.getAttribute("data-folder");
let dragData;
try {
dragData = JSON.parse(e.dataTransfer.getData("application/json"));
} catch (err) {
console.error("Invalid drag data on breadcrumb:", err);
return;
}
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
if (filesToMove.length === 0) return;
fetch("moveFiles.php", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute("content")
},
body: JSON.stringify({
source: dragData.sourceFolder,
files: filesToMove,
destination: dropFolder
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast(`File(s) moved successfully to ${dropFolder}!`);
loadFileList(dragData.sourceFolder);
} else {
showToast("Error moving files: " + (data.error || "Unknown error"));
}
})
.catch(error => {
console.error("Error moving files via drop on breadcrumb:", error);
showToast("Error moving files.");
});
});
});
// --- NEW: Breadcrumb Delegation Setup ---
export function setupBreadcrumbDelegation() {
const container = document.getElementById("fileListTitle");
if (!container) {
console.error("Breadcrumb container (fileListTitle) not found.");
return;
}
// Remove any previous delegation listeners if necessary
container.removeEventListener("click", breadcrumbClickHandler);
container.removeEventListener("dragover", breadcrumbDragOverHandler);
container.removeEventListener("dragleave", breadcrumbDragLeaveHandler);
container.removeEventListener("drop", breadcrumbDropHandler);
// Attach delegated listeners
container.addEventListener("click", breadcrumbClickHandler);
container.addEventListener("dragover", breadcrumbDragOverHandler);
container.addEventListener("dragleave", breadcrumbDragLeaveHandler);
container.addEventListener("drop", breadcrumbDropHandler);
}
// Click handler via delegation
function breadcrumbClickHandler(e) {
// Look for the nearest ancestor with the "breadcrumb-link" class.
const link = e.target.closest(".breadcrumb-link");
if (!link) return;
e.stopPropagation();
e.preventDefault(); // Prevent default link behavior if needed
const folder = link.getAttribute("data-folder");
window.currentFolder = folder;
localStorage.setItem("lastOpenedFolder", folder);
// Update the container with sanitized breadcrumbs.
// (If you prefer, you can render the breadcrumbs into a dedicated container
// and update the title separately.)
const container = document.getElementById("fileListTitle");
const sanitizedBreadcrumb = DOMPurify.sanitize(renderBreadcrumb(folder));
container.innerHTML = "Files in (" + sanitizedBreadcrumb + ")";
expandTreePath(folder);
document.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
const targetOption = document.querySelector(`.folder-option[data-folder="${folder}"]`);
if (targetOption) targetOption.classList.add("selected");
loadFileList(folder);
}
// Dragover handler via delegation
function breadcrumbDragOverHandler(e) {
const link = e.target.closest(".breadcrumb-link");
if (!link) return;
e.preventDefault();
link.classList.add("drop-hover");
}
// Dragleave handler via delegation
function breadcrumbDragLeaveHandler(e) {
const link = e.target.closest(".breadcrumb-link");
if (!link) return;
link.classList.remove("drop-hover");
}
// Drop handler via delegation
function breadcrumbDropHandler(e) {
const link = e.target.closest(".breadcrumb-link");
if (!link) return;
e.preventDefault();
link.classList.remove("drop-hover");
const dropFolder = link.getAttribute("data-folder");
let dragData;
try {
dragData = JSON.parse(e.dataTransfer.getData("application/json"));
} catch (err) {
console.error("Invalid drag data on breadcrumb:", err);
return;
}
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
if (filesToMove.length === 0) return;
fetch("moveFiles.php", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute("content")
},
body: JSON.stringify({
source: dragData.sourceFolder,
files: filesToMove,
destination: dropFolder
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast(`File(s) moved successfully to ${dropFolder}!`);
loadFileList(dragData.sourceFolder);
} else {
showToast("Error moving files: " + (data.error || "Unknown error"));
}
})
.catch(error => {
console.error("Error moving files via drop on breadcrumb:", error);
showToast("Error moving files.");
});
}
/* ----------------------
Check Current User's Folder-Only Permission
----------------------*/
@@ -380,7 +418,7 @@ export async function loadFolderTree(selectedFolder) {
const titleEl = document.getElementById("fileListTitle");
titleEl.innerHTML = "Files in (" + renderBreadcrumb(window.currentFolder) + ")";
bindBreadcrumbEvents();
setupBreadcrumbDelegation();
loadFileList(window.currentFolder);
const folderState = loadFolderTreeState();
@@ -404,7 +442,7 @@ export async function loadFolderTree(selectedFolder) {
localStorage.setItem("lastOpenedFolder", selected);
const titleEl = document.getElementById("fileListTitle");
titleEl.innerHTML = "Files in (" + renderBreadcrumb(selected) + ")";
bindBreadcrumbEvents();
setupBreadcrumbDelegation();
loadFileList(selected);
});
});