fix(security): reject array values on the event update route too

PUT /:id has the same weakness the create chains just had:
express-validator runs isIn/isBoolean/isInt element-wise, so
`image_quality: [72]` satisfies every check and stays an array. This
handler spreads req.body straight into the update, so the array reached
a scalar column — a PG insert error, and `[false]` read as true.

Covers all six fields in that block, not only the four this PR is about.
enable_devtools_protection and overlay_protection sit in the same list
with the identical flaw, and leaving two known holes next to four closed
ones would have been the odd choice.

Refs #1296
This commit is contained in:
Paul Nothaft
2026-09-05 08:33:03 +02:00
parent 0deef2584f
commit 8f3436f17d
+12 -7
View File
@@ -1591,13 +1591,18 @@ module.exports = (router) => {
body('source_mode').optional().isIn(['managed', 'reference']),
body('external_path').optional({ nullable: true }).isString().trim(),
body('require_password').optional().isBoolean(),
// Download protection settings
body('protection_level').optional().isIn(['basic', 'standard', 'enhanced', 'maximum']),
body('enable_devtools_protection').optional().isBoolean(),
body('use_canvas_rendering').optional().isBoolean(),
body('overlay_protection').optional().isBoolean(),
body('image_quality').optional().isInt({ min: 1, max: 100 }),
body('fragmentation_level').optional().isInt({ min: 1, max: 10 }),
// Download protection settings. .not().isArray() because
// express-validator runs isIn/isBoolean/isInt element-wise: a
// single-element array like `image_quality: [72]` satisfies every check
// and stays an array, and this handler spreads req.body straight into
// the update — so it reached a scalar column as an array (a PG error,
// and `[false]` read as true). Same guard as the create chain (#1296).
body('protection_level').optional().not().isArray().isIn(['basic', 'standard', 'enhanced', 'maximum']),
body('enable_devtools_protection').optional().not().isArray().isBoolean(),
body('use_canvas_rendering').optional().not().isArray().isBoolean(),
body('overlay_protection').optional().not().isArray().isBoolean(),
body('image_quality').optional().not().isArray().isInt({ min: 1, max: 100 }),
body('fragmentation_level').optional().not().isArray().isInt({ min: 1, max: 10 }),
body('password').optional().isString().custom((value) => {
if (value === undefined || value === null || value === '') {
return true;