CVE-2026-34574
Description
Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to versions 8.6.69 and 9.7.0-alpha.14, an authenticated user can bypass the immutability guard on session fields (expiresAt, createdWith) by sending a null value in a PUT request to the session update endpoint. This allows nullifying the session expiry, making the session valid indefinitely and bypassing configured session length policies. This issue has been patched in versions 8.6.69 and 9.7.0-alpha.14.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
parse-servernpm | >= 9.0.0, < 9.7.0-alpha.14 | 9.7.0-alpha.14 |
parse-servernpm | < 8.6.69 | 8.6.69 |
Affected products
14cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha11:*:*:*:node.js:*:*+ 13 more
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha11:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha12:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha13:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha2:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha3:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha4:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha5:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha6:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha7:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha8:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha9:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:*:*:*:*:*:node.js:*:*range: <8.6.69
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha1:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha10:*:*:*:node.js:*:*
Patches
2ebccd7fe2708fix: Session field immutability bypass via falsy-value guard ([GHSA-f6j3-w9v3-cq22](https://github.com/parse-community/parse-server/security/advisories/GHSA-f6j3-w9v3-cq22)) (#10348)
2 files changed · +156 −4
spec/ParseSession.spec.js+152 −0 modified@@ -393,4 +393,156 @@ describe('Parse.Session', () => { }); expect(verifyRes.data.expiresAt.iso).toBe(farFuture); }); + + it('should reject null expiresAt when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull1', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + const originalExpiresAt = sessionRes.data.expiresAt; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + expiresAt: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + + const verifyRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt); + }); + + it('should reject null createdWith when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull2', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + const originalCreatedWith = sessionRes.data.createdWith; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + createdWith: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + + const verifyRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + expect(verifyRes.data.createdWith).toEqual(originalCreatedWith); + }); + + it('should reject null installationId when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull3', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + installationId: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + }); + + it('should reject null sessionToken when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull4', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + sessionToken: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + }); });
src/RestWrite.js+4 −4 modified@@ -1174,13 +1174,13 @@ RestWrite.prototype.handleSession = function () { if (this.query) { if (this.data.user && !this.auth.isMaster && this.data.user.objectId != this.auth.user.id) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); - } else if (this.data.installationId) { + } else if ('installationId' in this.data) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); - } else if (this.data.sessionToken) { + } else if ('sessionToken' in this.data) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); - } else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) { + } else if ('expiresAt' in this.data && !this.auth.isMaster && !this.auth.isMaintenance) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); - } else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) { + } else if ('createdWith' in this.data && !this.auth.isMaster && !this.auth.isMaintenance) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); } if (!this.auth.isMaster) {
90802969fc71fix: Session field immutability bypass via falsy-value guard ([GHSA-f6j3-w9v3-cq22](https://github.com/parse-community/parse-server/security/advisories/GHSA-f6j3-w9v3-cq22)) (#10347)
2 files changed · +156 −4
spec/ParseSession.spec.js+152 −0 modified@@ -394,6 +394,158 @@ describe('Parse.Session', () => { expect(verifyRes.data.expiresAt.iso).toBe(farFuture); }); + it('should reject null expiresAt when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull1', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + const originalExpiresAt = sessionRes.data.expiresAt; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + expiresAt: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + + const verifyRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt); + }); + + it('should reject null createdWith when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull2', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + const originalCreatedWith = sessionRes.data.createdWith; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + createdWith: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + + const verifyRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + expect(verifyRes.data.createdWith).toEqual(originalCreatedWith); + }); + + it('should reject null installationId when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull3', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + installationId: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + }); + + it('should reject null sessionToken when updating a session via PUT', async () => { + const user = await Parse.User.signUp('sessionupdatenull4', 'password'); + const sessionToken = user.getSessionToken(); + + const sessionRes = await request({ + method: 'GET', + url: 'http://localhost:8378/1/sessions/me', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + }, + }); + const sessionId = sessionRes.data.objectId; + + const updateRes = await request({ + method: 'PUT', + url: `http://localhost:8378/1/sessions/${sessionId}`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + body: { + sessionToken: null, + }, + }).catch(e => e); + + expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); + }); + describe('PUT /sessions/me', () => { it('should return error with invalid session token', async () => { const response = await request({
src/RestWrite.js+4 −4 modified@@ -1235,13 +1235,13 @@ RestWrite.prototype.handleSession = function () { if (this.query) { if (this.data.user && !this.auth.isMaster && this.data.user.objectId != this.auth.user.id) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: user'); - } else if (this.data.installationId) { + } else if ('installationId' in this.data) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: installationId'); - } else if (this.data.sessionToken) { + } else if ('sessionToken' in this.data) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: sessionToken'); - } else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) { + } else if ('expiresAt' in this.data && !this.auth.isMaster && !this.auth.isMaintenance) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: expiresAt'); - } else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) { + } else if ('createdWith' in this.data && !this.auth.isMaster && !this.auth.isMaintenance) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: createdWith'); } if (!this.auth.isMaster) {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/parse-community/parse-server/commit/90802969fc713b7bc9733d7255c7519a6ed75d21nvdPatchWEB
- github.com/parse-community/parse-server/commit/ebccd7fe2708007e62f705ee1c820a6766178777nvdPatchWEB
- github.com/parse-community/parse-server/pull/10347nvdIssue TrackingPatchWEB
- github.com/parse-community/parse-server/pull/10348nvdIssue TrackingPatchWEB
- github.com/parse-community/parse-server/security/advisories/GHSA-f6j3-w9v3-cq22nvdPatchVendor AdvisoryWEB
- github.com/advisories/GHSA-f6j3-w9v3-cq22ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-34574ghsaADVISORY
News mentions
0No linked articles in our index yet.