Skip to content

Comments

fix: JWT Algorithm Confusion in Google Auth Adapter (GHSA-4q3h-vp4r-prv2)#10072

Merged
mtrezza merged 3 commits intoparse-community:alphafrom
mtrezza:fix/ghsa-4q3h-auth-adapter-alg-none
Feb 23, 2026
Merged

fix: JWT Algorithm Confusion in Google Auth Adapter (GHSA-4q3h-vp4r-prv2)#10072
mtrezza merged 3 commits intoparse-community:alphafrom
mtrezza:fix/ghsa-4q3h-auth-adapter-alg-none

Conversation

@mtrezza
Copy link
Member

@mtrezza mtrezza commented Feb 23, 2026

Pull Request

Issue

JWT Algorithm Confusion in Google Auth Adapter (GHSA-4q3h-vp4r-prv2).

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

  • Bug Fixes

    • Enforced RS256 for JWT verification across Apple, Facebook, and Google adapters to reject forged/invalid tokens.
    • Improved error handling when a signing key cannot be found.
  • Enhancements

    • Google authentication now retrieves and caches signing keys via JWKS with configurable cache options.
  • Tests

    • Added tests covering algorithm-forgery rejection, missing signing keys, and consistent RS256 enforcement.

@parse-github-assistant
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title fix: ghsa 4q3h fix: Ghsa 4q3h Feb 23, 2026
@parse-github-assistant
Copy link

parse-github-assistant bot commented Feb 23, 2026

🚀 Thanks for opening this pull request!

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

JWT verification hardened across Apple, Facebook, and Google adapters: header-provided alg is no longer trusted (RS256 enforced). Google adapter switched to JWKS-based signing-key retrieval with caching and explicit error when key not found. Tests updated/added to cover forgery and key-handling scenarios.

Changes

Cohort / File(s) Summary
Test suite
spec/AuthenticationAdapters.spec.js, spec/index.spec.js
Added tests for forged alg:none tokens, enforced RS256 verification, and missing Google signing key. Updated mocks to use flat token header fields, introduced fakeSigningKey, and standardized getSigningKey / jwt.verify stubs.
Apple & Facebook adapters
src/Adapters/Auth/apple.js, src/Adapters/Auth/facebook.js
Removed extraction of alg from token header; jwt.verify now explicitly restricts algorithms to ['RS256'] (ignores header alg).
Google adapter + helper
src/Adapters/Auth/google.js
Replaced legacy PEM/http key logic with jwks-rsa JWKS lookup and caching (new options: cacheMaxEntries, cacheMaxAge). Added getGoogleKeyByKeyId helper, use JWKS-derived signing key, enforce RS256, and throw when no matching key found.
Misc tests update
spec/...
Aligned other adapter tests (Apple, Facebook) to expect RS256 enforcement and to mock getSigningKey where applicable.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Adapter as Auth Adapter
    participant JWKS as JWKS Cache/Client
    participant jwtLib as jwt.verify

    Client->>Adapter: send id_token
    Adapter->>Adapter: extract kid from token header
    Adapter->>JWKS: getSigningKey(kid, cache options)
    JWKS-->>Adapter: signingKey (or error if not found)
    Adapter->>jwtLib: verify(token, signingKey, algorithms:['RS256'], audience: clientId)
    jwtLib-->>Adapter: verified claims (or verification error)
    Adapter-->>Client: auth result / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete; critical sections like Issue and Approach contain only template placeholders with no actual implementation details provided. Complete the Issue section with the actual security vulnerability being fixed and provide detailed explanation of the approach, changes, and rationale in the Approach section.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and cryptic, using only an acronym 'GHSA' without context or description of the actual security fix. Expand the title to clearly describe the fix, e.g., 'fix: Prevent JWT algorithm substitution attacks in authentication adapters' or similar descriptive phrasing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Feb 23, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
spec/AuthenticationAdapters.spec.js (1)

503-519: Minor: consider also asserting the error message, not just the code.

The test correctly validates that a forged alg:none token is rejected with Parse.Error.OBJECT_NOT_FOUND. For robustness, you could also assert the error message contains a jwt-related reason (e.g., expect(e.message).toMatch(/invalid/) or similar) to ensure the rejection comes from jwt.verify's algorithm check rather than some other code path. This is optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/AuthenticationAdapters.spec.js` around lines 503 - 519, Add an assertion
that the thrown error's message indicates a JWT/algorithm issue in the existing
test that calls google.validateAuthData (the 'should reject forged alg:none
JWT...' test): after catching the error (variable e) and asserting e.code ===
Parse.Error.OBJECT_NOT_FOUND, also assert e.message matches a jwt-related
pattern (e.g. /invalid|algorithm|jwt/i) to ensure the rejection is due to jwt
verification and not another code path; keep the existing spyOn(authUtils,
'getSigningKey') and forgedToken setup unchanged.
src/Adapters/Auth/google.js (1)

112-117: Redundant audience check — jwt.verify already validates this.

The audience: clientId option on line 94 causes jwt.verify to throw if the audience doesn't match. This manual check at lines 112-117 is unreachable when clientId is truthy (jwt.verify throws first), and skipped when clientId is falsy. Consider removing it to reduce dead code.

Note: this predates this PR, so feel free to defer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Adapters/Auth/google.js` around lines 112 - 117, Remove the redundant
manual audience validation that compares jwtClaims.aud to clientId and throws a
Parse.Error; jwt.verify already enforces the audience when called with the
audience: clientId option. Specifically, delete the if (clientId &&
jwtClaims.aud !== clientId) { throw new Parse.Error(...); } block (which
references jwtClaims, clientId and Parse.Error) so the code relies on jwt.verify
for audience checks and avoid unreachable/dead code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@spec/AuthenticationAdapters.spec.js`:
- Around line 503-519: Add an assertion that the thrown error's message
indicates a JWT/algorithm issue in the existing test that calls
google.validateAuthData (the 'should reject forged alg:none JWT...' test): after
catching the error (variable e) and asserting e.code ===
Parse.Error.OBJECT_NOT_FOUND, also assert e.message matches a jwt-related
pattern (e.g. /invalid|algorithm|jwt/i) to ensure the rejection is due to jwt
verification and not another code path; keep the existing spyOn(authUtils,
'getSigningKey') and forgedToken setup unchanged.

In `@src/Adapters/Auth/google.js`:
- Around line 112-117: Remove the redundant manual audience validation that
compares jwtClaims.aud to clientId and throws a Parse.Error; jwt.verify already
enforces the audience when called with the audience: clientId option.
Specifically, delete the if (clientId && jwtClaims.aud !== clientId) { throw new
Parse.Error(...); } block (which references jwtClaims, clientId and Parse.Error)
so the code relies on jwt.verify for audience checks and avoid unreachable/dead
code.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9935fc7 and 99bb908.

📒 Files selected for processing (4)
  • spec/AuthenticationAdapters.spec.js
  • src/Adapters/Auth/apple.js
  • src/Adapters/Auth/facebook.js
  • src/Adapters/Auth/google.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 23, 2026
@mtrezza mtrezza changed the title fix: Ghsa 4q3h fix: JWT Algorithm Confusion in Google Auth Adapter (GHSA-4q3h-vp4r-prv2) Feb 23, 2026
@mtrezza
Copy link
Member Author

mtrezza commented Feb 23, 2026

Commit message:

fix: JWT Algorithm Confusion in Google Auth Adapter ([GHSA-4q3h-vp4r-prv2](https://github.com/parse-community/parse-server/security/advisories/GHSA-4q3h-vp4r-prv2))

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
spec/index.spec.js (2)

676-676: alg: 'RS256' in fakeDecodedToken is dead test data — consider removing or inverting it

The hardened adapters no longer read alg from the token header (that's the exact vulnerability being fixed). Keeping alg: 'RS256' in the mock silently implies the adapter may still use it. Removing it keeps the mock honest; alternatively, setting it to alg: 'none' would make this test simultaneously confirm that the header's alg is ignored even when adversarially set.

♻️ Suggested change
-        const fakeDecodedToken = { kid: '123', alg: 'RS256' };
+        const fakeDecodedToken = { kid: '123', alg: 'none' }; // alg is intentionally ignored by the hardened adapter
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/index.spec.js` at line 676, Test mock includes a misleading alg header;
update the fakeDecodedToken used in the spec (variable fakeDecodedToken) to
either remove the alg property entirely or set alg: 'none' so the test no longer
implies the adapter reads header alg — change the fakeDecodedToken declaration
accordingly (remove alg or set to 'none') and re-run the tests to ensure they
still validate that header.alg is ignored.

662-689: Test still uses the done-callback pattern — prefer async/await

♻️ Suggested refactor
-  it('should not fail when Google signin is introduced without the optional clientId', done => {
+  it('should not fail when Google signin is introduced without the optional clientId', async () => {
     const jwt = require('jsonwebtoken');
     const authUtils = require('../lib/Adapters/Auth/utils');

-    reconfigureServer({
+    await reconfigureServer({
       auth: { google: {} },
-    })
-      .then(() => {
-        const fakeClaim = {
-          iss: 'https://accounts.google.com',
-          aud: 'secret',
-          exp: Date.now(),
-          sub: 'the_user_id',
-        };
-        const fakeDecodedToken = { kid: '123', alg: 'RS256' };
-        const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
-        spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
-        spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
-        spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
-        const user = new Parse.User();
-        user
-          .linkWith('google', {
-            authData: { id: 'the_user_id', id_token: 'the_token' },
-          })
-          .then(done);
-      })
-      .catch(done.fail);
+    });
+    const fakeClaim = {
+      iss: 'https://accounts.google.com',
+      aud: 'secret',
+      exp: Date.now(),
+      sub: 'the_user_id',
+    };
+    const fakeDecodedToken = { kid: '123', alg: 'RS256' };
+    const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
+    spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
+    spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
+    spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
+    const user = new Parse.User();
+    await user.linkWith('google', {
+      authData: { id: 'the_user_id', id_token: 'the_token' },
+    });
   });

Based on learnings: new and modified tests in the parse-server repository should use async/await with promise-based patterns rather than callback patterns with done().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/index.spec.js` around lines 662 - 689, The test "should not fail when
Google signin is introduced without the optional clientId" still uses the done
callback pattern; convert it to an async function and use await instead: make
the spec async, await reconfigureServer(...), set up the spies
(authUtils.getHeaderFromToken, authUtils.getSigningKey, jwt.verify) as before,
then await user.linkWith('google', { authData: {...} }) and let the test
complete by returning from the async function (remove done and done.fail).
Ensure the test name and spy references (authUtils.getHeaderFromToken,
authUtils.getSigningKey, jwt.verify, user.linkWith) remain the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/index.spec.js`:
- Around line 677-679: The test mock for authUtils.getSigningKey (the
fakeSigningKey used in the spyOn(...).and.resolveTo(...) call) supplies only
rsaPublicKey but the adapter checks googleKey.publicKey first (in google.js), so
update the fakeSigningKey to include a publicKey property (e.g., { kid: '123',
publicKey: 'the_rsa_public_key' }) so the test exercises the normal code path;
leave the getHeaderFromToken spy as-is.

---

Nitpick comments:
In `@spec/index.spec.js`:
- Line 676: Test mock includes a misleading alg header; update the
fakeDecodedToken used in the spec (variable fakeDecodedToken) to either remove
the alg property entirely or set alg: 'none' so the test no longer implies the
adapter reads header alg — change the fakeDecodedToken declaration accordingly
(remove alg or set to 'none') and re-run the tests to ensure they still validate
that header.alg is ignored.
- Around line 662-689: The test "should not fail when Google signin is
introduced without the optional clientId" still uses the done callback pattern;
convert it to an async function and use await instead: make the spec async,
await reconfigureServer(...), set up the spies (authUtils.getHeaderFromToken,
authUtils.getSigningKey, jwt.verify) as before, then await
user.linkWith('google', { authData: {...} }) and let the test complete by
returning from the async function (remove done and done.fail). Ensure the test
name and spy references (authUtils.getHeaderFromToken, authUtils.getSigningKey,
jwt.verify, user.linkWith) remain the same.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99bb908 and 3ae3c59.

📒 Files selected for processing (1)
  • spec/index.spec.js

@mtrezza mtrezza merged commit 9d5942d into parse-community:alpha Feb 23, 2026
21 of 22 checks passed
parseplatformorg pushed a commit that referenced this pull request Feb 23, 2026
## [9.3.1-alpha.4](9.3.1-alpha.3...9.3.1-alpha.4) (2026-02-23)

### Bug Fixes

* JWT Algorithm Confusion in Google Auth Adapter ([GHSA-4q3h-vp4r-prv2](https://github.com/parse-community/parse-server/security/advisories/GHSA-4q3h-vp4r-prv2)) ([#10072](#10072)) ([9d5942d](9d5942d))
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 9.3.1-alpha.4

@parseplatformorg parseplatformorg added the state:released-alpha Released as alpha version label Feb 23, 2026
@mtrezza mtrezza deleted the fix/ghsa-4q3h-auth-adapter-alg-none branch February 23, 2026 22:11
@codecov
Copy link

codecov bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.61%. Comparing base (a5269f0) to head (3ae3c59).
⚠️ Report is 3 commits behind head on alpha.

Additional details and impacted files
@@           Coverage Diff           @@
##            alpha   #10072   +/-   ##
=======================================
  Coverage   92.60%   92.61%           
=======================================
  Files         191      191           
  Lines       15745    15711   -34     
  Branches      176      176           
=======================================
- Hits        14581    14551   -30     
+ Misses       1152     1148    -4     
  Partials       12       12           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:released-alpha Released as alpha version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants