Skip to content

Security: Fix SQL injection and authorization vulnerabilities - #4591

Merged
SteRiccio merged 2 commits into
masterfrom
claude/brave-tesla-auejdo
Oct 29, 2026
Merged

SteRiccio merged 2 commits into
masterfrom
claude/brave-tesla-auejdo

Conversation

@SteRiccio

Copy link
Copy Markdown
Member

Description

This PR addresses multiple security vulnerabilities related to SQL injection and authorization checks across the application:

  • SQL Injection Prevention

    • Fixed parameterized query handling in record, survey, user, category, taxonomy, and analysis repositories to use proper parameter binding instead of string interpolation
    • Added validation in expression parser (toSql.ts) to reject prototype pollution attempts (e.g., constructor, toString, __proto__ as operators)
    • Added sanitization of sort order values to only accept ASC or DESC
    • Added filename validation for export downloads to match expected pattern export_{surveyId}_{timestamp}.zip
  • Authorization Improvements

    • Added recordUuid parameter validation in record node file endpoints to ensure the node belongs to the requested record
    • Added userUuid parameter validation in user update endpoints to prevent users from modifying other users' data
    • Implemented requireRecordsMergePermission middleware to verify users can edit both source and target records before merging
    • Added requireMergeRecordsMatchUserGroupQualifiers middleware for group-based access control on merge operations
    • Fixed email visibility filtering in user list endpoint based on Authorizer.canViewOtherUsersEmail permission
    • Added validation in password reset flow to ensure user is actually invited to the survey
  • API Response Security

    • Modified /auth/reset-password endpoint to never return the reset token in the response (token is only sent via email)
    • Changed to use Request.getPublicServerUrl() instead of Request.getServerUrl() for password reset URLs
  • Code Quality

    • Added unit tests for SQL sanitization in expression parser and sort handling
    • Refactored user creation/update logic to separate concerns and improve validation flow
    • Improved parameter passing consistency across database queries

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Security fix

How has this been tested

  • Added unit tests (061queryToSqlSanitization.test.ts) covering:
    • Unary operator validation in expression parser
    • Rejection of prototype pollution attempts
    • Sort order sanitization
  • Existing authorization middleware tests continue to pass
  • All parameterized query changes maintain backward compatibility with existing functionality

Do you consider this PR needs further testing?

  • No
  • Yes

Security-sensitive endpoints (record merge, user updates, file downloads, password reset) should be tested in a staging environment to verify authorization checks work correctly across different user roles and group qualifiers.

Disclaimer

These are critical security fixes addressing SQL injection and authorization bypass vulnerabilities. All parameter binding changes maintain the same query semantics while preventing injection attacks. The authorization checks are additive and do not break existing legitimate use cases.

https://claude.ai/code/session_015iBJ7y2dfG18xPFFCtmYEz

Authorization:
- reset-password: do not return the reset token in the response; build the
  email link from the public server URL
- user edit routes: update only the user in the path (never insert), insert
  only via POST /user; email can be changed only by system admins also
  outside a survey
- reset password URL of a survey user is returned only while invited
- persist node: the node must belong to the record checked by the middleware
- records step update requires the analysis permission; records merge
  requires edit permission (and group qualifiers) on both records
- record node file download: the node must belong to the record in the path
- survey users list: hide other users' emails unless allowed

SQL injection:
- surveys list/count: lang, limit, offset passed as query params, sort
  order normalized
- records summary: sort order normalized, limit/offset passed as params
  (":value" is not quoted)
- category items: parentUuid and lang passed as params
- users lists, chains list, chain by uuid: limit/offset/uuid as params
- query to SQL: only known unary operators and ASC/DESC sort orders,
  operator/function lookups restricted to own properties

Path traversal:
- records export download: only export_<surveyId>_<ts>.zip files
- surveys list export download: validate the temp file name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iBJ7y2dfG18xPFFCtmYEz
@SteRiccio
SteRiccio requested a balanced review from Copilot October 29, 2026 09:15
@SteRiccio SteRiccio self-assigned this Oct 29, 2026
@mergify

mergify Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

An unresolved moderate issue remains where an undefined lang parameter reaches the repository query, causing pg-promise to throw and break the surveys list endpoint when no language is supplied.

1 open issue
What changed in this PR

This PR hardens several server endpoints against SQL injection and authorization bypass. It replaces string-interpolated SQL fragments (sort order, language keys, parent/chain UUIDs, limit/offset) with bound parameters across the record, survey, user, category, taxonomy, and analysis repositories, and tightens the expression-to-SQL compiler against prototype-pollution-style operator/type lookups. On the authorization side, it adds ownership/identity checks (record↔node, user↔userUuid), a dedicated records-merge permission plus group-qualifier middleware, email-visibility filtering in the user list, and stops leaking the password-reset token in the API response.

Changes:

  • SQL hardening: parameterize sort order (DbOrder.normalize), lang, parentUuid, chainUuid, and limit/offset; sanitize unary/binary operators and expression types to own-properties only.
  • Authorization: requireRecordsMergePermission + requireMergeRecordsMatchUserGroupQualifiers, node-belongs-to-record checks, userUuid match on updates, export filename validation, and email filtering via canViewOtherUsersEmail.
  • API response security: /auth/reset-password no longer returns the token and uses getPublicServerUrl; new unit tests for SQL sanitization.
File Description
test/​unit/​tests/​061queryToSqlSanitization.test.ts New tests for operator/type sanitization and sort order (imports and expectations verified)
server/​modules/​user/​service/​userService.js Adds invited-check on reset password and system-admin-only email edit outside surveys
server/​modules/​user/​repository/​userRepository.js Binds limit/offset instead of interpolation
server/​modules/​user/​api/​userApi.js Splits insert/update, validates userUuid, filters emails by permission
server/​modules/​taxonomy/​repository/​taxonomyRepository.js Binds limit
server/​modules/​survey/​repository/​surveyRepository.js Binds lang/limit/offset, normalizes sort order — lang can be undefined (see comment)
server/​modules/​survey/​api/​surveyApi.js Validates temp export filename
server/​modules/​record/​repository/​recordRepository.js Binds limit/offset, normalizes sort order
server/​modules/​record/​api/​recordQualifierMiddleware.ts Adds merge-records group-qualifier variant
server/​modules/​record/​api/​recordApi.js Node↔record ownership checks, export filename regex, merge permission wiring
server/​modules/​category/​repository/​categoryRepository.js Binds parentUuid/lang
server/​modules/​auth/​authApiMiddleware.js New requireRecordsMergePermission
server/​modules/​auth/​api/​authApi.js Stops returning reset token; uses public server URL
server/​modules/​analysis/​repository/​chain/​read.js Binds limit/offset/chainUuid
core/​expressionParser/​toSql/​toSql.ts Own-property-only operator/type lookups; restricts unary operators
common/​model/​query/​sort/​sort.js Sanitizes sort order to ASC/DESC
common/​model/​db/​tables/​chain/​select.js Binds chainUuid in WHERE

Key finding: One functional regression — in surveyRepository.fetchUserSurveys, lang is passed to a pg-promise named parameter without being coerced to null. The webapp's surveys list call omits lang, so it arrives as undefined, and the query references $/lang/, which makes pg-promise throw. countUserSurveys already defaults lang = null, confirming the infor the inconsistency. Details and a suggested fix are in the inline comment. The authorization logic (merge permissions, group qualifiers) and the recordService.getManyByUuids call warrant staging validation across user roles.

Review effort: Balanced

Comment thread server/modules/survey/repository/surveyRepository.js
…query params

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iBJ7y2dfG18xPFFCtmYEz
@sonarqubecloud

Copy link
Copy Markdown

@SteRiccio
SteRiccio requested a balanced review from Copilot October 29, 2026 09:46
@SteRiccio
SteRiccio merged commit 6ce328e into master Oct 29, 2026
13 checks passed
@SteRiccio
SteRiccio deleted the claude/brave-tesla-auejdo branch October 29, 2026 09:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A critical unresolved issue remains where fetchUserSurveys passes an undefined lang to pg-promise, causing runtime failures in callers that omit a language.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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.

3 participants