@@ -1142,6 +1142,59 @@ Query.prototype.select = function select() {
11421142 throw new TypeError ( 'Invalid select() argument. Must be string or object.' ) ;
11431143} ;
11441144
1145+ /**
1146+ * Sets this query's `sanitizeProjection` option. If set, `sanitizeProjection` does
1147+ * two things:
1148+ *
1149+ * 1. Enforces that projection values are numbers, not strings.
1150+ * 2. Prevents using `+` syntax to override properties that are deselected by default.
1151+ *
1152+ * With `sanitizeProjection()`, you can pass potentially untrusted user data to `.select()`.
1153+ *
1154+ * #### Example
1155+ *
1156+ * const userSchema = new Schema({
1157+ * name: String,
1158+ * password: { type: String, select: false }
1159+ * });
1160+ * const UserModel = mongoose.model('User', userSchema);
1161+ * const { _id } = await UserModel.create({ name: 'John', password: 'secret' })
1162+ *
1163+ * // The MongoDB server has special handling for string values that start with '$'
1164+ * // in projections, which can lead to unexpected leaking of sensitive data.
1165+ * let doc = await UserModel.findOne().select({ name: '$password' });
1166+ * doc.name; // 'secret'
1167+ * doc.password; // undefined
1168+ *
1169+ * // With `sanitizeProjection`, Mongoose forces all projection values to be numbers
1170+ * doc = await UserModel.findOne().sanitizeProjection(true).select({ name: '$password' });
1171+ * doc.name; // 'John'
1172+ * doc.password; // undefined
1173+ *
1174+ * // By default, Mongoose supports projecting in `password` using `+password`
1175+ * doc = await UserModel.findOne().select('+password');
1176+ * doc.password; // 'secret'
1177+ *
1178+ * // With `sanitizeProjection`, Mongoose prevents projecting in `password` and other
1179+ * // fields that have `select: false` in the schema.
1180+ * doc = await UserModel.findOne().sanitizeProjection(true).select('+password');
1181+ * doc.password; // undefined
1182+ *
1183+ * @method sanitizeProjection
1184+ * @memberOf Query
1185+ * @instance
1186+ * @param {Boolean } value
1187+ * @return {Query } this
1188+ * @see sanitizeProjection https://thecodebarbarian.com/whats-new-in-mongoose-5-13-sanitizeprojection.html
1189+ * @api public
1190+ */
1191+
1192+ Query . prototype . sanitizeProjection = function sanitizeProjection ( value ) {
1193+ this . _mongooseOptions . sanitizeProjection = value ;
1194+
1195+ return this ;
1196+ } ;
1197+
11451198/**
11461199 * Determines the MongoDB nodes from which to read.
11471200 *
@@ -4872,7 +4925,17 @@ Query.prototype._applyPaths = function applyPaths() {
48724925 return ;
48734926 }
48744927 this . _fields = this . _fields || { } ;
4875- helpers . applyPaths ( this . _fields , this . model . schema ) ;
4928+
4929+ let sanitizeProjection = undefined ;
4930+ if ( this . model != null && utils . hasUserDefinedProperty ( this . model . db . options , 'sanitizeProjection' ) ) {
4931+ sanitizeProjection = this . model . db . options . sanitizeProjection ;
4932+ } else if ( this . model != null && utils . hasUserDefinedProperty ( this . model . base . options , 'sanitizeProjection' ) ) {
4933+ sanitizeProjection = this . model . base . options . sanitizeProjection ;
4934+ } else {
4935+ sanitizeProjection = this . _mongooseOptions . sanitizeProjection ;
4936+ }
4937+
4938+ helpers . applyPaths ( this . _fields , this . model . schema , sanitizeProjection ) ;
48764939
48774940 let _selectPopulatedPaths = true ;
48784941
0 commit comments