Conversation
device heartbeat
sim selection via api
fix device heartbeat
delayed sms sending feature
heartbeat fallback
enhance app ui
support filters for received sms
Android UI enhancement
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| return await this.deviceModel.findByIdAndUpdate( | ||
| deviceId, | ||
| { $set: input }, | ||
| { $set: updateData }, |
Check failure
Code scanning / CodeQL
Database query built from user-controlled sources High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 1 month ago
In general, to fix this kind of issue, you must ensure that user-controlled data is treated strictly as values and not as query/update operators. For MongoDB/Mongoose updates, that means never passing a raw user object directly to operators like $set, but instead constructing a new object that only contains whitelisted, expected fields and simple literal values. This both prevents operator injection and guards against mass‑assignment of unintended properties.
For this specific case, the fix is to build updateData explicitly from allowed fields of RegisterDeviceInputDTO instead of { ...input }. Additionally, for any nested object like simInfo, we should build that subdocument field-by-field and/or treat the entire simInfo as a literal by placing it under a non-operator key. We should also ensure we do not allow users to override internal fields (e.g., user, _id) or to add arbitrary top-level keys.
Concretely, in api/src/gateway/gateway.service.ts:
- In
registerDevice, instead ofconst deviceData: any = { ...input, user }, constructdeviceDataby whitelisting known properties ofRegisterDeviceInputDTO(e.g.,model,brand,buildId,name,appVersionCode,simInfo,enabled, etc.). This ensures thatdeviceDatacannot contain unexpected MongoDB operators on properties that will later be reused in updates. - In
updateDevice, replaceconst updateData: any = { ...input }with a new object that only includes explicitly allowed properties (same whitelist as above) and buildssimInfoas a plain object. BecausefindByIdAndUpdateis using{ $set: updateData }, we must ensureupdateDataitself cannot contain any$-prefixed keys. - Preserve existing logic for setting default
name, normalizingenabled, and enrichingsimInfo.lastUpdated.
This change is localized to gateway.service.ts and does not change API behavior from a legitimate client’s perspective, but it removes the reliance on a tainted, unstructured object.
| @@ -46,7 +46,16 @@ | ||
| buildId: input.buildId, | ||
| }) | ||
|
|
||
| const deviceData: any = { ...input, user } | ||
| // Build device data explicitly from allowed fields to avoid propagating arbitrary keys | ||
| const deviceData: any = { | ||
| user, | ||
| model: input.model, | ||
| brand: input.brand, | ||
| buildId: input.buildId, | ||
| name: input.name, | ||
| appVersionCode: input.appVersionCode, | ||
| enabled: input.enabled, | ||
| } | ||
|
|
||
| // Set default name to "brand model" if not provided | ||
| if (!deviceData.name && input.brand && input.model) { | ||
| @@ -98,7 +107,15 @@ | ||
| input.enabled = true; | ||
| } | ||
|
|
||
| const updateData: any = { ...input } | ||
| // Build update data from a whitelist of allowed fields to prevent NoSQL injection | ||
| const updateData: any = { | ||
| model: input.model, | ||
| brand: input.brand, | ||
| buildId: input.buildId, | ||
| name: input.name, | ||
| appVersionCode: input.appVersionCode, | ||
| enabled: input.enabled, | ||
| } | ||
|
|
||
| // Handle simInfo if provided | ||
| if (input.simInfo) { |
| const existingSMS = await this.smsModel.findOne({ | ||
| device: device._id, | ||
| type: SMSType.RECEIVED, | ||
| sender: dto.sender, | ||
| message: dto.message, | ||
| receivedAt: { | ||
| $gte: toleranceStart, | ||
| $lte: toleranceEnd, | ||
| }, | ||
| }) |
Check failure
Code scanning / CodeQL
Database query built from user-controlled sources High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 1 month ago
In general, to fix this kind of NoSQL injection risk, you must ensure that any user-controlled data used in a MongoDB query is treated strictly as a literal value and cannot be interpreted as a query object or operator. This is commonly done by (a) validating that the input is of an expected primitive type (string/number/boolean) before using it in the query, or (b) explicitly wrapping the value in an operator like $eq, which forces MongoDB to interpret the data as a scalar comparison value.
For this specific code, the best minimal fix without changing existing functionality is to (1) add runtime checks that dto.sender and dto.message are strings and that receivedAt is a valid Date, and (2) wrap the user-controlled fields in $eq in the findOne query. This keeps the semantics (find an SMS with exact same device, type, sender, message, and a receivedAt within a tolerance window) but prevents any possibility of passing an object that Mongo could interpret as query operators.
Concretely in api/src/gateway/gateway.service.ts:
- In
receiveSMS, augment the existing validation block to also verifytypeof dto.sender === 'string'andtypeof dto.message === 'string'. Also check thatreceivedAtresolves to a validDate(i.e., notInvalid Date) before using it. - Update the
this.smsModel.findOne({ ... })call so thatsenderandmessageare expressed as{ $eq: dto.sender }and{ $eq: dto.message }.deviceandtypeare either ObjectId/enum from server-side code, so they do not need this treatment.
No new methods or external libraries are required; only in-place validation and query changes in receiveSMS are needed. All changes occur within the shown snippet of GatewayService.receiveSMS.
| @@ -657,7 +657,9 @@ | ||
| if ( | ||
| (!dto.receivedAt && !dto.receivedAtInMillis) || | ||
| !dto.sender || | ||
| !dto.message | ||
| !dto.message || | ||
| typeof dto.sender !== 'string' || | ||
| typeof dto.message !== 'string' | ||
| ) { | ||
| console.log('Invalid received SMS data') | ||
| throw new HttpException( | ||
| @@ -679,6 +681,17 @@ | ||
| ? new Date(dto.receivedAtInMillis) | ||
| : dto.receivedAt | ||
|
|
||
| if (!(receivedAt instanceof Date) || isNaN(receivedAt.getTime())) { | ||
| console.log('Invalid receivedAt value') | ||
| throw new HttpException( | ||
| { | ||
| success: false, | ||
| error: 'Invalid received SMS data', | ||
| }, | ||
| HttpStatus.BAD_REQUEST, | ||
| ) | ||
| } | ||
|
|
||
| // Deduplication: Check for existing SMS with same device, sender, message, and receivedAt (within ±5 seconds tolerance) | ||
| const toleranceMs = 5000 // 5 seconds | ||
| const toleranceStart = new Date(receivedAt.getTime() - toleranceMs) | ||
| @@ -687,8 +700,8 @@ | ||
| const existingSMS = await this.smsModel.findOne({ | ||
| device: device._id, | ||
| type: SMSType.RECEIVED, | ||
| sender: dto.sender, | ||
| message: dto.message, | ||
| sender: { $eq: dto.sender }, | ||
| message: { $eq: dto.message }, | ||
| receivedAt: { | ||
| $gte: toleranceStart, | ||
| $lte: toleranceEnd, |
allow sim-selection when sending sms from dashboard
fix sim selection dropdown bug
No description provided.