2 Commits

Author SHA1 Message Date
1fbfc28780 Merge pull request 'security: vulnerability fixes & security hardening (2026-03-12)' (#1) from secops/security-update-2026-03-12 into release-candidate
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is passing
Reviewed-on: #1
2026-03-12 17:27:34 +01:00
76dc23c861 security: fix vulnerabilities and update security hardening (2026-03-12)
Code security fixes:
- Fixed 3 critical auth bypass bugs (user.jso, typo → user.json) in RenameFile, NewFolder, DeleteFolder API routes
- Added URL validation (HTTP/HTTPS only) on ProfilPicture and BackgroundCustom endpoints to prevent stored XSS/CSS injection
- Added path traversal protection in Upload.js (resolved path boundary check)
- Removed unsafe-eval from CSP script-src directive
- Removed information disclosure in BuildMetaData error responses
- Removed unused child_process import in BuildMetaData.js

Version bump: 1.2.1-beta → 1.2.2-beta
2026-03-12 17:16:16 +01:00
9 changed files with 35 additions and 8 deletions

View File

@@ -12,7 +12,7 @@ const securityHeadersMiddleware = (req, res, next) => {
'Content-Security-Policy', 'Content-Security-Policy',
[ [
"default-src 'self'", "default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://code.jquery.com https://cdnjs.cloudflare.com https://maxcdn.bootstrapcdn.com https://cdn.jsdelivr.net https://cdn.tailwindcss.com", "script-src 'self' 'unsafe-inline' https://code.jquery.com https://cdnjs.cloudflare.com https://maxcdn.bootstrapcdn.com https://cdn.jsdelivr.net https://cdn.tailwindcss.com",
"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com", "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
"img-src 'self' data: https: blob:", "img-src 'self' data: https: blob:",
"font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com", "font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@cdn-app/insider-myaxrin-labs-dinawo", "name": "@cdn-app/insider-myaxrin-labs-dinawo",
"version": "1.2.1-beta", "version": "1.2.2-beta",
"description": "", "description": "",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {

View File

@@ -1,7 +1,6 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const os = require('os'); const os = require('os');
const child_process = require('child_process');
const packageJson = require('../package.json'); const packageJson = require('../package.json');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -64,7 +63,7 @@ router.get('/', async (req, res) => {
res.json(buildMetadata); res.json(buildMetadata);
} catch (error) { } catch (error) {
console.error('Error in /build-metadata: ', error); console.error('Error in /build-metadata: ', error);
res.status(500).send('Error in /build-metadata: ' + error.toString()); res.status(500).send('Internal server error');
} }
}); });

View File

@@ -13,6 +13,16 @@ router.post('/wallpaper', (req, res) => {
return res.status(400).send('No wallpaper URL provided.'); return res.status(400).send('No wallpaper URL provided.');
} }
// Validate URL to prevent XSS/CSS injection via malicious URLs
try {
const parsed = new URL(wallpaperUrl);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return res.status(400).send('Invalid URL protocol. Only HTTP/HTTPS allowed.');
}
} catch {
return res.status(400).send('Invalid URL format.');
}
updateUserWallpaper(userId, wallpaperUrl, res); updateUserWallpaper(userId, wallpaperUrl, res);
}); });

View File

@@ -129,9 +129,9 @@ function authenticateToken(req, res, next) {
return res.status(401).json({ message: 'Unauthorized' }); return res.status(401).json({ message: 'Unauthorized' });
} }
fs.readFile(path.join(__dirname, '../../../data', 'user.jso,'), 'utf8', (err, data) => { fs.readFile(path.join(__dirname, '../../../data', 'user.json'), 'utf8', (err, data) => {
if (err) { if (err) {
console.error('Error reading user.jso,:', err); console.error('Error reading user.json:', err);
return res.status(401).json({ message: 'Unauthorized' }); return res.status(401).json({ message: 'Unauthorized' });
} }

View File

@@ -105,7 +105,7 @@ function authenticateToken(req, res, next) {
return res.status(401).json({ message: 'Unauthorized' }); return res.status(401).json({ message: 'Unauthorized' });
} }
fs.readFile(path.join(__dirname, '../../../data', 'user.jso,'), 'utf8', (err, data) => { fs.readFile(path.join(__dirname, '../../../data', 'user.json'), 'utf8', (err, data) => {
if (err) { if (err) {
console.error('Error reading user.json:', err); console.error('Error reading user.json:', err);
return res.status(401).json({ message: 'Unauthorized' }); return res.status(401).json({ message: 'Unauthorized' });

View File

@@ -13,6 +13,16 @@ router.post('/', (req, res) => {
return res.status(400).send('No profile picture URL provided.'); return res.status(400).send('No profile picture URL provided.');
} }
// Validate URL to prevent XSS/injection via malicious URLs
try {
const parsed = new URL(profilePictureUrl);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return res.status(400).send('Invalid URL protocol. Only HTTP/HTTPS allowed.');
}
} catch {
return res.status(400).send('Invalid URL format.');
}
updateUserProfilePicture(userId, profilePictureUrl, res); updateUserProfilePicture(userId, profilePictureUrl, res);
}); });

View File

@@ -107,7 +107,7 @@ function authenticateToken(req, res, next) {
} }
} }
fs.readFile(path.join(__dirname, '../../../data', 'user.jso,'), 'utf8', (err, data) => { fs.readFile(path.join(__dirname, '../../../data', 'user.json'), 'utf8', (err, data) => {
if (err) { if (err) {
console.error('Error reading user.json:', err); console.error('Error reading user.json:', err);
return res.status(401).json({ message: 'Unauthorized' }); return res.status(401).json({ message: 'Unauthorized' });

View File

@@ -68,6 +68,14 @@ router.post('/', (req, res) => {
const filename = fields.filename ? fields.filename[0] : file.originalFilename; const filename = fields.filename ? fields.filename[0] : file.originalFilename;
const filePath = path.join(userDir, filename); const filePath = path.join(userDir, filename);
// Path traversal protection: ensure resolved path stays within user directory
const resolvedUserDir = path.resolve(process.cwd(), 'cdn-files', userName);
const resolvedFilePath = path.resolve(filePath);
if (!resolvedFilePath.startsWith(resolvedUserDir + path.sep) && resolvedFilePath !== resolvedUserDir) {
if (fs.existsSync(file.path)) fs.unlinkSync(file.path);
return res.status(403).send('Path traversal detected');
}
// Récupérer les champs supplémentaires // Récupérer les champs supplémentaires
const expiryDate = fields.expiryDate ? fields.expiryDate[0] : ''; const expiryDate = fields.expiryDate ? fields.expiryDate[0] : '';
const password = fields.password ? fields.password[0] : ''; const password = fields.password ? fields.password[0] : '';