Feat: Path Verification (Security #4)

This commit is contained in:
MasterAcnolo
2026-01-14 21:47:38 +01:00
parent adcee62386
commit 489a4766fa
3 changed files with 76 additions and 31 deletions

29
main.js
View File

@@ -101,21 +101,44 @@ async function createMainWindow() {
});
}
function validateDownloadPath(userPath) {
const userHome = os.homedir(); // C:\Users\<User>
if (!userPath) return path.join(userHome, "Downloads", "Freedom Loader");
// Résolution canonique et suivi des symlinks
const resolved = fs.realpathSync(path.resolve(userPath));
const normalizedHome = path.resolve(userHome) + path.sep;
if (!resolved.startsWith(normalizedHome)) {
throw new Error("Chemin non autorisé : uniquement les sous-dossiers du dossier utilisateur sont permis !");
}
return resolved;
}
// IPC
ipcMain.handle("select-download-folder", async () => {
try {
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
if (!result.canceled && result.filePaths.length > 0) {
logger.info(`Dossier sélectionné : ${result.filePaths[0]}`);
return result.filePaths[0];
const validatedPath = validateDownloadPath(result.filePaths[0]);
logger.info(`Folder Checked and Valid : ${validatedPath}`);
return validatedPath;
}
return null;
} catch (err) {
logger.error(`Error when creating Output Folder : ${err.message}`);
logger.error(`An Error Occured when validating folder : ${err.message}`);
return null;
}
});
ipcMain.handle("validate-download-path", (event, userPath) => {
return validateDownloadPath(userPath);
});
ipcMain.handle("get-default-download-path", () => defaultDownloadPath);
ipcMain.on("set-progress", (event, percent) => {

View File

@@ -4,7 +4,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
setProgress: (percent) => ipcRenderer.send("set-progress", percent),
getFeatures: () => ipcRenderer.invoke("features")
getFeatures: () => ipcRenderer.invoke("features"),
getValidatedDownloadPath: (path) => ipcRenderer.invoke("validate-download-path", path)
});
// Contrôles de fenêtre et outils custom pour la topbar

View File

@@ -1,41 +1,62 @@
window.addEventListener("DOMContentLoaded", async () => {
const savePathElem = document.getElementById("savePath");
const form = document.getElementById("downloadForm");
// Essayer de charger depuis le localStorage
let savedPath = localStorage.getItem("customDownloadPath");
// Sinon demander le chemin par défaut à l'API Electron
if (!savedPath) {
savedPath = await window.electronAPI.getDefaultDownloadPath();
}
// Afficher le chemin
if (savePathElem) {
savePathElem.textContent = savedPath;
}
// Créer l'input caché s'il n'existe pas déjà
// input caché = DERNIER chemin validé par le back
let hidden = document.getElementById("savePathInput");
if (!hidden) {
hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "savePath";
hidden.id = "savePathInput";
document.getElementById("downloadForm").appendChild(hidden);
form.appendChild(hidden);
}
hidden.value = savedPath;
// Gestion du bouton de modification
document.getElementById("changePath").addEventListener("click", async () => {
const selectedPath = await window.electronAPI.selectDownloadFolder();
if (selectedPath) {
// Met à jour l'affichage
savePathElem.textContent = selectedPath;
hidden.value = selectedPath;
async function applyPathFromBack(path) {
savePathElem.textContent = path;
hidden.value = path;
localStorage.setItem("customDownloadPath", path); // UX only
}
// Et le stocke en localStorage pour la prochaine fois
localStorage.setItem("customDownloadPath", selectedPath);
async function loadInitialPath() {
// On affiche ce que le user a vu la dernière fois (UX)
const cached = localStorage.getItem("customDownloadPath");
if (cached) {
savePathElem.textContent = cached;
}
});
});
// MAIS la source de vérité reste le back
try {
const validatedPath = await window.electronAPI.getValidatedDownloadPath(
cached
);
await applyPathFromBack(validatedPath);
} catch {
// fallback sûr
const defaultPath =
await window.electronAPI.getDefaultDownloadPath();
await applyPathFromBack(defaultPath);
}
}
await loadInitialPath();
document
.getElementById("changePath")
.addEventListener("click", async () => {
try {
const selectedPath =
await window.electronAPI.selectDownloadFolder();
if (!selectedPath) return; // annulé
// Validation back obligatoire
const validatedPath =
await window.electronAPI.getValidatedDownloadPath(selectedPath);
await applyPathFromBack(validatedPath);
} catch (err) {
alert("Dossier non autorisé.");
}
});
});