security: comprehensive hardening across frontend, backend, and infrastructure
- Disable production source maps and hide nginx version - Reduce JSON body limit from 10gb to 50mb (uploads use multer, not JSON) - Strip database info and error details from health endpoint - Mask reCAPTCHA secret key in admin settings API responses - Whitelist sort/order query parameters in events and photos endpoints - Stop reflecting arbitrary origins in static file CORS headers - Align nginx security headers with backend Helmet CSP, remove deprecated X-XSS-Protection - Strip EXIF metadata from generated thumbnails and hero images - Bind postgres/redis dev ports to localhost in docker-compose configs - Add safeExec utility (spawn with shell:false) to prevent command injection - Convert all exec/execAsync calls in backup, restore, and database backup services to use safe spawn-based helpers
This commit is contained in:
@@ -495,8 +495,9 @@ router.get('/', adminAuth, requirePermission('events.view'), async (req, res) =>
|
||||
const offset = (page - 1) * limit;
|
||||
const search = req.query.search || '';
|
||||
const status = req.query.status || 'all';
|
||||
const sortBy = req.query.sortBy || 'created_at';
|
||||
const sortOrder = req.query.sortOrder || 'desc';
|
||||
const allowedSortBy = ['created_at', 'event_name', 'slug', 'updated_at', 'expires_at', 'capture_date'];
|
||||
const sortBy = allowedSortBy.includes(req.query.sortBy) ? req.query.sortBy : 'created_at';
|
||||
const sortOrder = ['asc', 'desc'].includes(req.query.sortOrder) ? req.query.sortOrder : 'desc';
|
||||
|
||||
// Build query
|
||||
let query = db('events');
|
||||
|
||||
@@ -727,7 +727,8 @@ router.get('/:eventId/photos/:photoId/download', adminAuth, requirePermission('p
|
||||
router.get('/:eventId/photos', adminAuth, requirePermission('photos.view'), async (req, res) => {
|
||||
try {
|
||||
const { eventId } = req.params;
|
||||
const { category_id, type, search, sort = 'date', order = 'desc' } = req.query;
|
||||
const { category_id, type, search, sort = 'date' } = req.query;
|
||||
const order = ['asc', 'desc'].includes(req.query.order) ? req.query.order : 'desc';
|
||||
|
||||
let query = db('photos')
|
||||
.where({ 'photos.event_id': eventId })
|
||||
|
||||
@@ -122,6 +122,11 @@ router.get('/', adminAuth, requirePermission('settings.view'), async (req, res)
|
||||
}
|
||||
});
|
||||
|
||||
// Mask sensitive secrets before sending to client
|
||||
if (settingsObject.security_recaptcha_secret_key) {
|
||||
settingsObject.security_recaptcha_secret_key = '••••••••';
|
||||
}
|
||||
|
||||
res.json(settingsObject);
|
||||
} catch (error) {
|
||||
console.error('Settings fetch error:', error);
|
||||
@@ -160,6 +165,11 @@ router.get('/:type', adminAuth, requirePermission('settings.view'), async (req,
|
||||
}
|
||||
});
|
||||
|
||||
// Mask sensitive secrets before sending to client
|
||||
if (settingsObject.security_recaptcha_secret_key) {
|
||||
settingsObject.security_recaptcha_secret_key = '••••••••';
|
||||
}
|
||||
|
||||
res.json(settingsObject);
|
||||
} catch (error) {
|
||||
console.error('Settings fetch error:', error);
|
||||
|
||||
Reference in New Issue
Block a user