Skip to content

fix(security): prevent NoSQL injection from user-controlled query data (SonarQube jssecurity:S5147) - #221

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/sonarqube-fix-AZhSVLrd4wErqc9Ey1Y3-1787475874
Open

fix(security): prevent NoSQL injection from user-controlled query data (SonarQube jssecurity:S5147)#221
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/sonarqube-fix-AZhSVLrd4wErqc9Ey1Y3-1787475874

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 23, 2026

Copy link
Copy Markdown

Summary

loginHandler in routes/index.js built the Mongo login query straight from the parsed JSON body, so a request could inject query operators instead of values.

Attack: POST /login with {"username": "admin@snyk.io", "password": {"$gt": ""}} produces {password: {$gt: ""}}, which matches regardless of the real password and grants an admin session ($ne, $regex behave the same).

Fix:

var username = typeof req.body.username === 'string' ? req.body.username : '';
var password = typeof req.body.password === 'string' ? req.body.password : '';
if (validator.isEmail(username)) {
  User.find({ username: { $eq: username } }, function (err, users) {   // was: { username, password } from req.body
    ...  // password compared in JS via crypto.timingSafeEqual, never part of the query
  • Both fields are coerced to strings, so an object can never reach the query.
  • The username is pinned with $eq, so even a string value cannot be reinterpreted as an operator document.
  • The password is no longer sent to Mongo at all; it is compared in constant time against the stored value.
  • The inner const username = req.body.username shadow was dropped so the audit log / redirect use the validated value.

Other SonarQube findings in this file are intentionally untouched. The pre-existing build check failure (Snyk SARIF upload) reproduces on other open PRs and is unrelated.

Devin-Org: engineering

Link to Devin session: https://app.devin.ai/sessions/e811c410695844f987bdec383d7c6f3b
Requested by: @joao-cognition


Devin Review

Status Commit
⚪ Not started

Run Devin Review

Devin Review (Staging)

…5147)

Co-Authored-By: Joao Esteves <joao.esteves@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Author

Remediates SonarQube issue AZhSVLrd4wErqc9Ey1Y3 (rule jssecurity:S5147 — "Change this code to not construct database queries directly from user-controlled data", BLOCKER, routes/index.js:39).

What was wrong: exports.loginHandler built the Mongo query directly from the parsed JSON body: User.find({ username: req.body.username, password: req.body.password }, ...). Because Express' JSON body parser yields arbitrary types, a request body such as {"username": "admin@snyk.io", "password": {"$gt": ""}} injects a MongoDB operator rather than a value, so the query matches the admin user regardless of password and adminLoginSuccess grants a logged-in session. validator.isEmail only guarded the username, not the password, and did not constrain types.

What changed: both fields are coerced/validated to strings before the query is constructed — non-string input becomes '', so no object can reach Mongo and be interpreted as an operator. The inner const username = req.body.username shadow inside the callback was removed so the audit log and redirect use the validated value.

Scope is limited to this single finding; other SonarQube issues in routes/index.js are intentionally untouched. The issue was not marked resolved or false-positive in SonarQube.

Comment thread routes/index.js Fixed
…onstant time (SonarQube jssecurity:S5147)

Co-Authored-By: Joao Esteves <joao.esteves@cognition.ai>
Comment thread routes/index.js Fixed
…S5147)

Co-Authored-By: Joao Esteves <joao.esteves@cognition.ai>
Comment thread routes/index.js
if (validator.isEmail(req.body.username)) {
User.find({ username: req.body.username, password: req.body.password }, function (err, users) {
if (users.length > 0) {
var username = typeof req.body.username === 'string' ? req.body.username : '';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant