Skip to content

Commit 9a04a01

Browse files
authored
Merge branch 'release-8.x.x' into fix/insta-auth-8xx
2 parents df854b9 + 0b032a3 commit 9a04a01

10 files changed

+99
-24
lines changed

changelogs/CHANGELOG_release.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [8.6.1](https://github.com/parse-community/parse-server/compare/8.6.0...8.6.1) (2025-12-14)
2+
3+
4+
### Bug Fixes
5+
6+
* Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) ([#9986](https://github.com/parse-community/parse-server/issues/9986)) ([12d8b50](https://github.com/parse-community/parse-server/commit/12d8b502a2f99098d177d095842b07d55f62313a))
7+
18
# [8.6.0](https://github.com/parse-community/parse-server/compare/8.5.0...8.6.0) (2025-12-10)
29

310

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "8.6.0",
3+
"version": "8.6.1",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

public/de-AT/email_verification_link_expired.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
<body>
1515
<h1>{{appName}}</h1>
1616
<h1>Expired verification link!</h1>
17-
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
18-
<input name="token" type="hidden" value="{{{token}}}">
19-
<input name="locale" type="hidden" value="{{{locale}}}">
17+
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
18+
<input name="token" type="hidden" value="{{token}}">
19+
<input name="locale" type="hidden" value="{{locale}}">
2020
<button type="submit">Resend Link</button>
2121
</form>
2222
</body>

public/de-AT/password_reset.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ <h1>Reset Your Password</h1>
2323
<p>You can set a new Password for your account: {{username}}</p>
2424
<br />
2525
<p>{{error}}</p>
26-
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
26+
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
2727
<input name='utf-8' type='hidden' value='' />
28-
<input name="username" type="hidden" id="username" value="{{{username}}}" />
29-
<input name="token" type="hidden" id="token" value="{{{token}}}" />
30-
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
28+
<input name="username" type="hidden" id="username" value="{{username}}" />
29+
<input name="token" type="hidden" id="token" value="{{token}}" />
30+
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
3131

3232
<p>New Password</p>
3333
<input name="new_password" type="password" id="password" />

public/de/email_verification_link_expired.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
<body>
1515
<h1>{{appName}}</h1>
1616
<h1>Expired verification link!</h1>
17-
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
18-
<input name="token" type="hidden" value="{{{token}}}">
19-
<input name="locale" type="hidden" value="{{{locale}}}">
17+
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
18+
<input name="token" type="hidden" value="{{token}}">
19+
<input name="locale" type="hidden" value="{{locale}}">
2020
<button type="submit">Resend Link</button>
2121
</form>
2222
</body>

public/de/password_reset.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ <h1>Reset Your Password</h1>
2323
<p>You can set a new Password for your account: {{username}}</p>
2424
<br />
2525
<p>{{error}}</p>
26-
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
26+
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
2727
<input name='utf-8' type='hidden' value='' />
28-
<input name="username" type="hidden" id="username" value="{{{username}}}" />
29-
<input name="token" type="hidden" id="token" value="{{{token}}}" />
30-
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
28+
<input name="username" type="hidden" id="username" value="{{username}}" />
29+
<input name="token" type="hidden" id="token" value="{{token}}" />
30+
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
3131

3232
<p>New Password</p>
3333
<input name="new_password" type="password" id="password" />

public/email_verification_link_expired.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
<body>
1515
<h1>{{appName}}</h1>
1616
<h1>Expired verification link!</h1>
17-
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
18-
<input name="token" type="hidden" value="{{{token}}}">
19-
<input name="locale" type="hidden" value="{{{locale}}}">
17+
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
18+
<input name="token" type="hidden" value="{{token}}">
19+
<input name="locale" type="hidden" value="{{locale}}">
2020
<button type="submit">Resend Link</button>
2121
</form>
2222
</body>

public/password_reset.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ <h1>Reset Your Password</h1>
2323
<p>You can set a new Password for your account: {{username}}</p>
2424
<br />
2525
<p>{{error}}</p>
26-
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
26+
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
2727
<input name='utf-8' type='hidden' value='' />
28-
<input name="username" type="hidden" id="username" value="{{{username}}}" />
29-
<input name="token" type="hidden" id="token" value="{{{token}}}" />
30-
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
28+
<input name="username" type="hidden" id="username" value="{{username}}" />
29+
<input name="token" type="hidden" id="token" value="{{token}}" />
30+
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
3131

3232
<p>New Password</p>
3333
<input name="new_password" type="password" id="password" />

spec/PagesRouter.spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,4 +1180,72 @@ describe('Pages Router', () => {
11801180
});
11811181
});
11821182
});
1183+
1184+
describe('XSS Protection', () => {
1185+
beforeEach(async () => {
1186+
await reconfigureServer({
1187+
appId: 'test',
1188+
appName: 'exampleAppname',
1189+
publicServerURL: 'http://localhost:8378/1',
1190+
pages: { enableRouter: true },
1191+
});
1192+
});
1193+
1194+
it('should escape XSS payloads in token parameter', async () => {
1195+
const xssPayload = '"><script>alert("XSS")</script>';
1196+
const response = await request({
1197+
url: `http://localhost:8378/1/apps/choose_password?token=${encodeURIComponent(xssPayload)}&username=test&appId=test`,
1198+
});
1199+
1200+
expect(response.status).toBe(200);
1201+
expect(response.text).not.toContain('<script>alert("XSS")</script>');
1202+
expect(response.text).toContain('&quot;&gt;&lt;script&gt;');
1203+
});
1204+
1205+
it('should escape XSS in username parameter', async () => {
1206+
const xssUsername = '<img src=x onerror=alert(1)>';
1207+
const response = await request({
1208+
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(xssUsername)}&appId=test`,
1209+
});
1210+
1211+
expect(response.status).toBe(200);
1212+
expect(response.text).not.toContain('<img src=x onerror=alert(1)>');
1213+
expect(response.text).toContain('&lt;img');
1214+
});
1215+
1216+
it('should escape XSS in locale parameter', async () => {
1217+
const xssLocale = '"><svg/onload=alert(1)>';
1218+
const response = await request({
1219+
url: `http://localhost:8378/1/apps/choose_password?locale=${encodeURIComponent(xssLocale)}&appId=test`,
1220+
});
1221+
1222+
expect(response.status).toBe(200);
1223+
expect(response.text).not.toContain('<svg/onload=alert(1)>');
1224+
expect(response.text).toContain('&quot;&gt;&lt;svg');
1225+
});
1226+
1227+
it('should handle legitimate usernames with quotes correctly', async () => {
1228+
const username = "O'Brien";
1229+
const response = await request({
1230+
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(username)}&appId=test`,
1231+
});
1232+
1233+
expect(response.status).toBe(200);
1234+
// Should be properly escaped as HTML entity
1235+
expect(response.text).toContain('O&#39;Brien');
1236+
// Should NOT contain unescaped quote that breaks HTML
1237+
expect(response.text).not.toContain('value="O\'Brien"');
1238+
});
1239+
1240+
it('should handle legitimate usernames with ampersands correctly', async () => {
1241+
const username = 'Smith & Co';
1242+
const response = await request({
1243+
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(username)}&appId=test`,
1244+
});
1245+
1246+
expect(response.status).toBe(200);
1247+
// Should be properly escaped
1248+
expect(response.text).toContain('Smith &amp; Co');
1249+
});
1250+
});
11831251
});

0 commit comments

Comments
 (0)