From 63eb0ebde92b2aa3e6720314634901e5ee9c0d17 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 20 Mar 2026 04:50:59 +0000 Subject: [PATCH 1/2] test: GHSA-8pjv-59c8-44p8 v9 --- spec/vulnerabilities.spec.js | 70 ++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/spec/vulnerabilities.spec.js b/spec/vulnerabilities.spec.js index e51cdad7ff..5581b78831 100644 --- a/spec/vulnerabilities.spec.js +++ b/spec/vulnerabilities.spec.js @@ -3750,4 +3750,74 @@ describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via Live expect(updateSpy).not.toHaveBeenCalled(); }); + describe('(GHSA-8pjv-59c8-44p8) SSRF via Webhook URL requires master key', () => { + it('rejects registering a webhook function with internal URL without master key', async () => { + await expectAsync( + request({ + method: 'POST', + url: Parse.serverURL + '/hooks/functions', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + }, + body: JSON.stringify({ + functionName: 'ssrf_probe', + url: 'http://169.254.169.254/latest/meta-data/iam/security-credentials/', + }), + }) + ).toBeRejected(); + }); + + it('rejects updating a webhook function URL to internal address without master key', async () => { + await expectAsync( + request({ + method: 'PUT', + url: Parse.serverURL + '/hooks/functions/ssrf_probe', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + }, + body: JSON.stringify({ + url: 'http://169.254.169.254/latest/meta-data/', + }), + }) + ).toBeRejected(); + }); + + it('rejects registering a webhook trigger with internal URL without master key', async () => { + await expectAsync( + request({ + method: 'POST', + url: Parse.serverURL + '/hooks/triggers', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + }, + body: JSON.stringify({ + className: 'TestClass', + triggerName: 'beforeSave', + url: 'http://127.0.0.1:8080/admin/status', + }), + }) + ).toBeRejected(); + }); + + it('rejects registering a webhook with internal URL using JavaScript key', async () => { + await expectAsync( + request({ + method: 'POST', + url: Parse.serverURL + '/hooks/functions', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-JavaScript-Key': 'test', + }, + body: JSON.stringify({ + functionName: 'ssrf_probe', + url: 'http://10.0.0.1:3000/internal-api', + }), + }) + ).toBeRejected(); + }); + }); + }); From 10d8c82061652fb4bdf85a6d2dc8a83749ce3ef3 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:06:04 +0000 Subject: [PATCH 2/2] test: assert specific 403 status and seed webhook for PUT test --- spec/vulnerabilities.spec.js | 39 ++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/spec/vulnerabilities.spec.js b/spec/vulnerabilities.spec.js index 5581b78831..126491b09b 100644 --- a/spec/vulnerabilities.spec.js +++ b/spec/vulnerabilities.spec.js @@ -3751,8 +3751,17 @@ describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via Live }); describe('(GHSA-8pjv-59c8-44p8) SSRF via Webhook URL requires master key', () => { + const expectMasterKeyRequired = async promise => { + try { + await promise; + fail('Expected request to be rejected'); + } catch (error) { + expect(error.status).toBe(403); + } + }; + it('rejects registering a webhook function with internal URL without master key', async () => { - await expectAsync( + await expectMasterKeyRequired( request({ method: 'POST', url: Parse.serverURL + '/hooks/functions', @@ -3765,11 +3774,25 @@ describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via Live url: 'http://169.254.169.254/latest/meta-data/iam/security-credentials/', }), }) - ).toBeRejected(); + ); }); it('rejects updating a webhook function URL to internal address without master key', async () => { - await expectAsync( + // Seed a legitimate webhook first so the PUT hits auth, not "not found" + await request({ + method: 'POST', + url: Parse.serverURL + '/hooks/functions', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + functionName: 'ssrf_probe', + url: 'https://example.com/webhook', + }), + }); + await expectMasterKeyRequired( request({ method: 'PUT', url: Parse.serverURL + '/hooks/functions/ssrf_probe', @@ -3781,11 +3804,11 @@ describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via Live url: 'http://169.254.169.254/latest/meta-data/', }), }) - ).toBeRejected(); + ); }); it('rejects registering a webhook trigger with internal URL without master key', async () => { - await expectAsync( + await expectMasterKeyRequired( request({ method: 'POST', url: Parse.serverURL + '/hooks/triggers', @@ -3799,11 +3822,11 @@ describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via Live url: 'http://127.0.0.1:8080/admin/status', }), }) - ).toBeRejected(); + ); }); it('rejects registering a webhook with internal URL using JavaScript key', async () => { - await expectAsync( + await expectMasterKeyRequired( request({ method: 'POST', url: Parse.serverURL + '/hooks/functions', @@ -3816,7 +3839,7 @@ describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via Live url: 'http://10.0.0.1:3000/internal-api', }), }) - ).toBeRejected(); + ); }); });