{"record":{"id":"f2cdaa3fa3f1c7b1","repo":"Automattic/mongoose","slug":"cast-to-number-failed-for-value-value-type-f2cdaa","errorCode":null,"errorMessage":"Cast to number failed for value \"${value}\" (type ${valueType}) at path \"${path}\"","messagePattern":"Cast to number failed for value \"(.+?)\" \\(type (.+?)\\) at path \"(.+?)\"","errorType":"validation","errorClass":"CastError","httpStatus":null,"severity":"error","filePath":"lib/schema/operators/bitwise.js","lineNumber":33,"sourceCode":"  if (Array.isArray(val)) {\n    return val.map(function(v) {\n      return _castNumber(_this.path, v);\n    });\n  } else if (Buffer.isBuffer(val)) {\n    return val;\n  }\n  // Assume trying to cast to number\n  return _castNumber(_this.path, val);\n}\n\n/*!\n * ignore\n */\n\nfunction _castNumber(path, num) {\n  const v = Number(num);\n  if (isNaN(v)) {\n    throw new CastError('number', num, path);\n  }\n  return v;\n}\n\nmodule.exports = handleBitwiseOperator;\n","sourceCodeStart":15,"sourceCodeEnd":39,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema/operators/bitwise.js#L15-L39","documentation":"handleBitwiseOperator (the handler for $bitsAllSet/$bitsAnySet/$bitsAllClear/$bitsAnyClear on Buffer/Int32/Number paths) accepts Buffers, numeric masks, or arrays of bit positions; every non-Buffer entry goes through `_castNumber`, which throws CastError 'number' when `Number(num)` is NaN. So a bitwise filter received a mask value that is not numeric — a garbage string, an object, or an array containing such entries.","triggerScenarios":"`Model.find({ flags: { $bitsAllSet: 'x1' } })` (non-numeric string), `{ flags: { $bitsAllSet: ['a', 'b'] } }` (array with non-numeric bit positions), or an object/undefined leaking in as the mask. Note `Buffer` masks pass through untouched; numbers and numeric strings are fine ('0x1f', '5', 31).","commonSituations":"Building bitwise feature-flag or permission filters from unvalidated config/user input; passing hex strings with a 0X prefix typo ('0X1F' actually parses — the breakers are values like 'true', '', or objects); mixing up bit-position arrays and bitmask numbers from documentation examples.","solutions":["Pass integer bitmasks (`{ $bitsAllSet: 0b101 }` = 5) or arrays of bit positions with integers only (`[0, 2]`)","Validate masks before querying: `Number.isInteger(mask) || (Array.isArray(mask) && mask.every(Number.isInteger))`","Sanitize config-driven masks at load time with the same check","Accept Buffer masks if flags are byte-oriented"],"exampleFix":"// before\nModel.find({ permissions: { $bitsAllSet: req.query.mask } }); // 'admin'\n\n// after\nconst mask = Number(req.query.mask);\nif (!Number.isInteger(mask) || mask < 0) {\n  return res.status(400).json({ error: 'mask must be an integer' });\n}\nModel.find({ permissions: { $bitsAllSet: mask } });","handlingStrategy":"validation","validationCode":"function isValidBitMask(v) {\n  if (Buffer.isBuffer(v)) return true;\n  if (Array.isArray(v)) return v.every(Number.isInteger);\n  return Number.isInteger(v);\n}\nif (!isValidBitMask(mask)) throw Object.assign(new Error('mask must be integer or array of integers'), { status: 400 });\nModel.find({ flags: { $bitsAllSet: mask } });","typeGuard":"function isBitMask(v) {\n  return Buffer.isBuffer(v) || Number.isInteger(v) || (Array.isArray(v) && v.every(Number.isInteger));\n}","tryCatchPattern":"try { await Model.find({ flags: { $bitsAllSet: mask } }); } catch (err) { if (err.name === 'CastError' && err.kind === 'number') { return badRequest('bit mask must be numeric'); } throw err; }","preventionTips":["Keep bitmasks as integers in config, not free-form strings","Validate feature-flag masks at config load time","Document bit-position arrays vs bitmask numbers per endpoint"],"tags":["mongoose","bitwise","query","operator","cast"],"backgroundTag":"mongoose-cast-error","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}