Repository navigation
Security: Fix SQL injection and authorization vulnerabilities - #4591
Conversation
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
|
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. |
There was a problem hiding this comment.
🟡 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, andlimit/offset; sanitize unary/binary operators and expression types to own-properties only. - Authorization:
requireRecordsMergePermission+requireMergeRecordsMatchUserGroupQualifiers, node-belongs-to-record checks,userUuidmatch on updates, export filename validation, and email filtering viacanViewOtherUsersEmail. - API response security:
/auth/reset-passwordno longer returns the token and usesgetPublicServerUrl; 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
…query params Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015iBJ7y2dfG18xPFFCtmYEz
|
There was a problem hiding this comment.
🔵 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




Description
This PR addresses multiple security vulnerabilities related to SQL injection and authorization checks across the application:
SQL Injection Prevention
toSql.ts) to reject prototype pollution attempts (e.g.,constructor,toString,__proto__as operators)ASCorDESCexport_{surveyId}_{timestamp}.zipAuthorization Improvements
recordUuidparameter validation in record node file endpoints to ensure the node belongs to the requested recorduserUuidparameter validation in user update endpoints to prevent users from modifying other users' datarequireRecordsMergePermissionmiddleware to verify users can edit both source and target records before mergingrequireMergeRecordsMatchUserGroupQualifiersmiddleware for group-based access control on merge operationsAuthorizer.canViewOtherUsersEmailpermissionAPI Response Security
/auth/reset-passwordendpoint to never return the reset token in the response (token is only sent via email)Request.getPublicServerUrl()instead ofRequest.getServerUrl()for password reset URLsCode Quality
Types of changes
How has this been tested
061queryToSqlSanitization.test.ts) covering:Do you consider this PR needs further testing?
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