fix(auth): prevent signup token issuance for existing accounts (CVE-2026-10130)#581
Conversation
…026-10130) The POST /signup/email endpoint always issued a valid api_token session cookie and returned success, even when the email already belonged to an existing account. The user/identity merge query unconditionally links the freshly generated token to the matched identity, so an attacker could sign up with a victim's email and any password to obtain an authenticated session for that account without knowing the password. Because User nodes are merged by email while email Identity nodes are keyed by (provider, provider_user_id), this also allowed taking over accounts that were originally created via Google/GitHub OAuth. Reject signup with 409 whenever a User or Identity already exists for the email (any provider), before any token is generated, and fail closed if the existence check errors. If account creation does not yield a new identity (e.g. a concurrent-signup race), return 500, issue no cookie, and clean up any linked token. Add regression tests covering the existing-account rejection, fail-closed behaviour, the legitimate new-user flow, and the creation-race path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completed Working on "Code Review"✅ Review submitted: COMMENT. Total comments: 2 across 1 files. ✅ Workflow completed successfully. |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF ScorecardScorecard details
Scanned Files
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds ChangesEmail Signup Security Fix
Playwright CI and DevDeps
Sequence Diagram(s) sequenceDiagram
participant Client
participant email_signup as email_signup_handler
participant OrgsGraph as OrganizationsGraph
participant TokenSvc as TokenService
participant EnsureUser as ensure_user_in_organizations
participant MailHash as _set_mail_hash
Client->>email_signup: POST /signup/email (email, password)
email_signup->>OrgsGraph: call `_email_account_exists(email)`
alt email exists
OrgsGraph-->>email_signup: exists
email_signup-->>Client: 409 Conflict
else email not found
email_signup->>TokenSvc: issue provisional api_token
email_signup->>EnsureUser: ensure_user_in_organizations(with token)
alt new_identity created
EnsureUser-->>email_signup: new_identity
email_signup->>MailHash: _set_mail_hash(new_identity, password_hash)
email_signup-->>Client: 201 Created + set `api_token` cookie
else creation failed / race
EnsureUser-->>email_signup: no new_identity
email_signup->>TokenSvc: delete_user_token(api_token)
email_signup-->>Client: 500 Internal Server Error
end
end
🎯 4 (Complex) | ⏱️ ~45 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
| # an attacker take over that account without knowing its password | ||
| # (CVE-2026-10130, authentication bypass via signup token issuance). | ||
| if await _email_account_exists(email): | ||
| logging.info("Signup attempt for existing account: %s", _sanitize_for_log(email)) |
There was a problem hiding this comment.
This is a false positive. The logged email is (a) validated against a strict regex ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ earlier in the handler, which rejects any CR/LF, and (b) passed through _sanitize_for_log(), which strips \r, \n and tabs before logging. CodeQL just doesn't model _sanitize_for_log as a sanitizer. This matches the existing logging convention used throughout this module, and the CodeQL analysis check itself passes.
| # Creation failed (e.g. DB error) or raced with a concurrent signup. | ||
| # Never issue a token in this case; clean up any token that was linked. | ||
| logging.error("Failed to create new user during signup: %s", | ||
| _sanitize_for_log(email)) |
There was a problem hiding this comment.
This is a false positive. The logged email is (a) validated against a strict regex ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ earlier in the handler, which rejects any CR/LF, and (b) passed through _sanitize_for_log(), which strips \r, \n and tabs before logging. CodeQL just doesn't model _sanitize_for_log as a sanitizer. This matches the existing logging convention used throughout this module, and the CodeQL analysis check itself passes.
|
|
||
| # Hash password | ||
| password_hash = _hash_password(password) | ||
| logging.info("New user created: %s", _sanitize_for_log(email)) |
There was a problem hiding this comment.
This is a false positive. The logged email is (a) validated against a strict regex ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ earlier in the handler, which rejects any CR/LF, and (b) passed through _sanitize_for_log(), which strips \r, \n and tabs before logging. CodeQL just doesn't model _sanitize_for_log as a sanitizer. This matches the existing logging convention used throughout this module, and the CodeQL analysis check itself passes.
There was a problem hiding this comment.
Final Review Summary
Findings by importance
- BLOCKER: 0
- CRITICAL: 0
- MAJOR: 2
- MINOR: 0
- SUGGESTION: 0
- PRAISE: 0
Key themes
- Failure-path robustness: cleanup failure handling can currently alter intended response behavior in hardened auth/signup error paths.
- Signup-path performance/query design: the account-existence check may introduce avoidable multiplicative work under load due to query shape.
Actionable next steps
- Make token cleanup in the non-new-identity branch best-effort (catch/log cleanup errors) so the API still returns the deterministic 500 response for that branch.
- Rewrite
_email_account_existsto use existence semantics (e.g.,EXISTS { ... }) rather than additive counts across optional matches to avoid Cartesian amplification. - After updates, run and confirm the new
tests/test_email_signup.pyscenarios still pass, especially race/failure branches.
| logging.error("Failed to create new user during signup: %s", | ||
| _sanitize_for_log(email)) | ||
| await delete_user_token(api_token) | ||
| return JSONResponse( |
There was a problem hiding this comment.
[major]: In the non-new-identity failure branch, delete_user_token(api_token) is awaited without guarding exceptions. If token cleanup fails, the handler will raise and skip the intended controlled 500 JSON response, creating inconsistent client behavior for the race/failure path this change is trying to harden.
There was a problem hiding this comment.
Thanks — looked into this. The handler does not actually break its controlled error path here, for two reasons:
delete_user_tokenalready catches and swallows its own exceptions internally (it logs and returns), so it never propagates out of this branch.- Even if it did raise, the surrounding
try/exceptinemail_signupreturns the same deterministic500 {"success": false, "error": "Registration failed"}response.
So the status code and body are identical regardless, and no token is ever returned to the client on this path. I've left the call as-is rather than adding a redundant inner guard, but happy to wrap it explicitly if you'd prefer defense-in-depth.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_email_signup.py`:
- Around line 61-64: Add the required pytest markers to the test function
test_existence_check_failure_fails_closed: add `@pytest.mark.unit` and
`@pytest.mark.auth` above the method decorator stack (alongside the existing
`@pytest.mark.asyncio` and `@patch` decorators) so the test is categorized correctly
for unit and auth runs.
- Around line 77-84: The test method test_new_account_is_created_with_token is
missing pytest classification markers; add `@pytest.mark.unit` and
`@pytest.mark.auth` above the test (e.g., directly above or alongside the existing
`@pytest.mark.asyncio` decorator) so the function has the unit and auth markers
while preserving the existing `@patch` decorators and AsyncMock usage in the
function signature.
- Around line 40-47: The test method
test_existing_account_is_rejected_without_token is missing required pytest
markers; add the decorators `@pytest.mark.unit` and `@pytest.mark.auth` above the
function (alongside the existing `@pytest.mark.asyncio`) so the function is
correctly categorized; ensure the new markers appear before the function
definition and above the patch decorators or in a consistent order with other
tests.
- Around line 99-107: The test method
test_non_new_identity_is_rejected_and_token_cleaned_up is missing pytest
markers; add the decorators `@pytest.mark.unit` and `@pytest.mark.auth` immediately
above the existing `@pytest.mark.asyncio` so the test is categorized correctly
(keep the existing patches and AsyncMock decorators intact) and ensure imports
for pytest markers are available if not already.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4feec536-2306-4ca7-b2b1-da8ef2daaf7d
📒 Files selected for processing (2)
api/routes/auth.pytests/test_email_signup.py
- Rewrite _email_account_exists as a UNION of two label-scoped, index-friendly lookups (LIMIT 1 per side). The previous chained OPTIONAL MATCH could form a Cartesian product (review feedback), and an aggregating WITH variant returned NULL under FalkorDB parameter binding, which would have made the check fail open. Verified the UNION query against FalkorDB for present/absent/identity-only cases. - Add tests pinning the helper's result interpretation (non-empty -> exists, empty -> not exists, query error propagates so the endpoint fails closed). - Regenerate root package-lock.json so `npm ci` is in sync with app/package.json after the @vitejs/plugin-react-swc bump (#554), unblocking the Playwright job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses CodeRabbit review: per AGENTS.md and the strict-markers config, test functions must carry a registered marker. Apply unit and auth module-wide. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
tests/test_email_signup.py (1)
141-159:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd the required pytest markers to these new helper tests.
These three async tests still need
@pytest.mark.unitand@pytest.mark.authso they participate in the expected test slices.🏷️ Proposed fix
+ `@pytest.mark.unit` + `@pytest.mark.auth` `@pytest.mark.asyncio` async def test_non_empty_result_means_account_exists(self): # UNION yields one row per matching User/Identity node. with self._patch_graph([["node-a"], ["node-b"]]): assert await _email_account_exists("taken@example.com") is True + `@pytest.mark.unit` + `@pytest.mark.auth` `@pytest.mark.asyncio` async def test_empty_result_means_no_account(self): with self._patch_graph([]): assert await _email_account_exists("free@example.com") is False + `@pytest.mark.unit` + `@pytest.mark.auth` `@pytest.mark.asyncio` async def test_query_error_propagates_to_fail_closed(self): """The helper must not swallow errors, so the endpoint can fail closed."""As per coding guidelines: Mark test functions with pytest markers:
e2e,slow,auth,integration,unit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_email_signup.py` around lines 141 - 159, The three async tests (test_non_empty_result_means_account_exists, test_empty_result_means_no_account, test_query_error_propagates_to_fail_closed) are missing required pytest markers; add `@pytest.mark.unit` and `@pytest.mark.auth` above each test (in addition to the existing `@pytest.mark.asyncio`) so they are included in the unit/auth test slices.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@tests/test_email_signup.py`:
- Around line 141-159: The three async tests
(test_non_empty_result_means_account_exists, test_empty_result_means_no_account,
test_query_error_propagates_to_fail_closed) are missing required pytest markers;
add `@pytest.mark.unit` and `@pytest.mark.auth` above each test (in addition to the
existing `@pytest.mark.asyncio`) so they are included in the unit/auth test
slices.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ca7d158e-4568-461e-b6e4-488e5cc11201
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
api/routes/auth.pytests/test_email_signup.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@package.json`:
- Around line 6-8: Add a short rationale for downgrading "`@playwright/test`" and
"playwright" from 1.57.0 to 1.56.1 in the PR description and the commit message
that touches package.json: state whether the downgrade is to mitigate test
stability/performance issues (link the Playwright issue e.g., GH `#38489` about
memory regressions and the Chrome-for-Testing default change) or whether it is
unrelated to CVE-2026-10130; if unrelated, move the version change into a
separate PR to keep the security fix narrowly scoped and reference the
issue/decision in the security PR.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 376d7758-4bb7-40fd-b18c-46c7109c4190
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (1)
package.json
| "@playwright/test": "^1.56.1", | ||
| "@types/node": "^22.10.2", | ||
| "playwright": "^1.57.0", | ||
| "playwright": "^1.56.1", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Playwright latest version 2026
💡 Result:
As of May 31, 2026, the latest stable version of Playwright is v1.60.0 [1][2]. This version was released on May 11, 2026 [1][3]. Playwright is widely used for web testing, automation, and increasingly for agentic web browsing workflows in 2026 [4][5]. Users can find the official release notes and documentation on the Playwright website [6]. Development remains active, with ongoing updates to browser versions and integration support across its supported languages, including the.NET implementation which also reached v1.60.0 in May 2026 [7][2].
Citations:
- 1: https://github.com/microsoft/playwright/releases/tag/v1.60.0
- 2: https://github.com/microsoft/playwright
- 3: https://currents.dev/posts/pw-1.60.0
- 4: https://winbuzzer.com/2026/05/25/microsoft-webwright-turns-web-agents-into-reusable-code-xcxwbn/
- 5: https://dev.to/alterlab/agentic-web-browsing-workflows-with-python-and-playwright-3nd9
- 6: https://playwright.dev/docs/release-notes
- 7: https://github.com/microsoft/playwright-dotnet/releases/tag/v1.60.0
🌐 Web query:
Playwright 1.57.0 security vulnerabilities or issues
💡 Result:
As of May 31, 2026, there are no direct, publicly documented security vulnerabilities explicitly assigned to Playwright version 1.57.0 in major vulnerability databases like Snyk [1]. While no critical vulnerabilities are specific to this version, users should be aware of the following context regarding security and performance: 1. Browser-Related Vulnerabilities: Playwright 1.57.0 introduced a significant architectural change by switching from Chromium to Chrome for Testing (CfT) builds [2][3][4]. Because Playwright uses these browser binaries, it remains indirectly susceptible to security vulnerabilities found in the underlying Chrome/Chromium engine [5][6]. Users must keep their Playwright installations updated to receive the latest browser patches provided by the Playwright team to mitigate these threats [5][6]. 2. Dependency and Utility Issues: Like many large software projects, Playwright periodically addresses potential vulnerabilities in its internal utilities. For example, recent maintenance in the repository has addressed issues such as polynomial backtracking in regular expression utilities used for log/output processing [7], which, while not a critical remote code execution flaw, represents the type of security hardening typically managed through ongoing updates. 3. Historical Context: A known critical vulnerability (GHSA-7mvr-c777-76hp) regarding improper SSL certificate validation (use of curl -k in installer scripts) was addressed in previous versions (fixed in v1.56.0) [8], and is not present in 1.57.0. 4. Operational Considerations: Some users reported performance and stability concerns following the upgrade to 1.57.0, specifically regarding higher memory usage attributed to the switch to Chrome for Testing [9]. While these are functional bugs rather than security vulnerabilities, they have impacted CI/CD environments where resource limits are strictly enforced [9]. For the most secure experience, it is recommended to monitor the official Playwright GitHub releases and security advisories, and to keep your Playwright package and its associated browser binaries updated to the latest available version [1][2].
Citations:
- 1: https://security.snyk.io/package/npm/playwright/1.57.0
- 2: https://redirect.github.com/microsoft/playwright/releases/tag/v1.57.0
- 3: https://github.com/microsoft/playwright/blob/54e92be7/docs/src/release-notes-csharp.md
- 4: https://playwright.dev/docs/release-notes
- 5: https://healsecurity.com/google-patches-151-vulnerabilities-in-chrome-including-22-critical-ones/
- 6: https://undercodenews.com/google-chrome-emergency-security-update-patches-16-vulnerabilities-including-critical-remote-code-execution-flaws/
- 7: fix(utils): fix polynomial backtracking in ansiRegex microsoft/playwright#40762
- 8: https://osv.dev/vulnerability/GHSA-7mvr-c777-76hp
- 9: [Bug]: Playwright 1.57 - No way to use open-source Chromium, Chrome for Testing causes high memory usage (20GB+ per instance) microsoft/playwright#38489
🌐 Web query:
Playwright 1.57.0 Chrome for Testing memory usage issue 38489 downgrade 1.56.1
💡 Result:
In Playwright version 1.57.0, the framework officially switched from using lightweight Chromium builds to Google Chrome for Testing (CfT) for both headed and headless browser execution [1][2]. Following this transition, some users reported significant increases in memory usage (e.g., reaching 20GB+ per instance) in GitHub issue #38489 [3][4]. For users experiencing this issue, downgrading to Playwright 1.56.1 remains a common troubleshooting step to restore the previous browser behavior and memory footprint while investigating the performance regression [3]. Key points regarding this transition: - Official Change: Playwright 1.57.0 moved to Chrome for Testing to ensure closer parity with the stable Chrome browser [1][2]. - Reported Behavior: Users on issue #38489 noted that even when specifying 'chromium' in their configuration, Playwright defaults to launching Chrome for Testing, which they observed consuming substantially more RAM than the prior Chromium versions [3]. - Recommendations: If you encounter unexpected memory pressure or system crashes after upgrading to v1.57.0, check if the issue persists without custom launch arguments [3]. If it does, downgrading to 1.56.1 can serve as an immediate mitigation to restore stability to your test environment [3]. Users should continue to monitor the official GitHub repository for updates or fixes regarding the high memory consumption associated with Chrome for Testing [3][5].
Citations:
- 1: https://redirect.github.com/microsoft/playwright/releases/tag/v1.57.0
- 2: https://playwright.dev/docs/release-notes
- 3: [Bug]: Playwright 1.57 - No way to use open-source Chromium, Chrome for Testing causes high memory usage (20GB+ per instance) microsoft/playwright#38489
- 4: [Bug]: Playwright 1.57 - No way to use open-source Chromium, Chrome for Testing causes high memory usage (20GB+ per instance) microsoft/playwright#38489
- 5: https://github.com/microsoft/playwright/actions/runs/25469820719/job/74731104188?pr=40685
🌐 Web query:
Playwright 1.56.1 release notes security advisories
💡 Result:
Playwright version 1.56.1 was released on October 17, 2025 [1][2]. The release notes for v1.56.1 focus on maintenance and internal improvements rather than security advisories [1][2]. Specifically, the changes included: - Allowing 'local-network-access' permission in Chromium [1]. - Removing 'workspaceFolder' references from the VS Code MCP [1]. - Renaming 'agents' to 'test agents' [1]. - Implementing a fallback to the current working directory (cwd) when resolving test configurations [1]. There are no specific security advisories associated with the v1.56.1 release [1][2]. As of May 2026, Playwright is on much newer versions (e.g., v1.60.0, released May 11, 2026) [3][4]. Users seeking information on recent vulnerabilities should consult the official Playwright GitHub repository for the most up-to-date security-related patches and browser engine updates, as many security concerns in browser automation are addressed by rolling to newer, patched Chromium versions [5][6].
Citations:
- 1: https://github.com/microsoft/playwright/releases/tag/v1.56.1
- 2: https://redirect.github.com/microsoft/playwright/releases/tag/v1.56.1
- 3: https://github.com/microsoft/playwright/releases/tag/v1.60.0
- 4: https://currents.dev/posts/pw-1.60.0
- 5: Chromium roll needed for CVE-2026-2441 (use-after-free, actively exploited) microsoft/playwright#39574
- 6: microsoft/playwright@cee6800
🌐 Web query:
Playwright 1.57.0 Chrome for Testing memory usage issue 38489 downgrade 1.56.1
💡 Result:
In Playwright version 1.57.0, a significant change was introduced where the framework transitioned from using lightweight, open-source Chromium builds to using Google's Chrome for Testing (CfT) for all Chromium-based operations [1]. This change is the subject of issue #38489, where users reported experiencing drastically increased memory usage (e.g., reaching 20GB+ per instance) and system instability when running tests under this newer version [2][3]. The issue report highlights that users are unable to revert to the previous Chromium-based behavior, as Playwright 1.57.0 forces the use of Chrome for Testing [2]. If you are encountering memory-related performance regressions or other compatibility issues (such as difficulties with specific drag-and-drop libraries) after updating to 1.57.0, downgrading to version 1.56.1 is a documented workaround used by the community to restore previous behavior [4]. To downgrade, you can use your package manager to reinstall the previous version: For npm: npm install --save-dev @playwright/test@1.56.1 For yarn: yarn add -D @playwright/test@1.56.1 As noted in the official release documentation, Chrome for Testing is now the default, and there is no native "switch" to return to the older open-source Chromium builds within the 1.57.x release line [2][1]. Staying on 1.56.1 allows you to maintain the previous browser environment until the memory usage or compatibility concerns are addressed in future updates [4].
Citations:
- 1: https://github.com/microsoft/playwright/blob/54e92be7/docs/src/release-notes-csharp.md
- 2: [Bug]: Playwright 1.57 - No way to use open-source Chromium, Chrome for Testing causes high memory usage (20GB+ per instance) microsoft/playwright#38489
- 3: [Bug]: Playwright 1.57 - No way to use open-source Chromium, Chrome for Testing causes high memory usage (20GB+ per instance) microsoft/playwright#38489
- 4: [Bug]: Drag and drop issues on 1.57.0 + html5sortable + chromium microsoft/playwright#38796
🌐 Web query:
Playwright 1.56.1 release notes security advisories
💡 Result:
Playwright version 1.56.1, released on October 17, 2025, did not include any security advisories [1]. The release primarily focused on minor maintenance and enhancements, including allowing local-network-access permissions in Chromium and updating Playwright Test Agent configurations [1][2]. There are no recorded security vulnerabilities associated with this specific version in the official release notes [1].
Citations:
- 1: https://github.com/microsoft/playwright/releases/tag/v1.56.1
- 2: microsoft/playwright@v1.56.0...v1.56.1
Document the rationale for the Playwright downgrade (1.57.0 → 1.56.1)
"`@playwright/test`": "^1.56.1",
"`@types/node`": "^22.10.2",
"playwright": "^1.56.1",
- Playwright 1.57.0 switched defaults to Chrome for Testing, and there are reports of major memory regressions (e.g., GitHub issue
#38489) where downgrading to 1.56.1 is used as a workaround. - PR objectives focus on CVE-2026-10130; please state whether the downgrade is for test stability/performance (and link the relevant issue), or if it’s unrelated and should move to a separate PR to keep the security fix narrowly scoped.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 6 - 8, Add a short rationale for downgrading
"`@playwright/test`" and "playwright" from 1.57.0 to 1.56.1 in the PR description
and the commit message that touches package.json: state whether the downgrade is
to mitigate test stability/performance issues (link the Playwright issue e.g.,
GH `#38489` about memory regressions and the Chrome-for-Testing default change) or
whether it is unrelated to CVE-2026-10130; if unrelated, move the version change
into a separate PR to keep the security fix narrowly scoped and reference the
issue/decision in the security PR.
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Fixes CVE-2026-10130 — Authentication Bypass via Email Signup Token Issuance for Existing Accounts.
POST /signup/emailalways issued a validapi_tokensession cookie and returnedsuccess: true, even when the email already belonged to an existing account. The user/identity merge query (_build_user_merge_query) unconditionally links the freshly generated token to the matched identity, so an attacker could sign up with a victim's email and any password to obtain an authenticated session for that account without knowing the password → account takeover.Because
Usernodes are merged by email while emailIdentitynodes are keyed by(provider, provider_user_id), this also allowed taking over accounts originally created via Google/GitHub OAuth (a new email identity attaches to the victim's existingUser).Fix
_email_account_exists(email)and reject signup with 409 whenever aUserorIdentityalready exists for the email (any provider), before any token is generated. Fails closed (exceptions propagate → 500, no token issued).ensure_user_in_organizationsis left unchanged, so OAuth flows are unaffected.Tests
New
tests/test_email_signup.py(4 tests, all passing):Full unit suite: 132 passed, 10 skipped. (The 4
test_simple_integration.pyerrors are pre-existing and reproduce on the unmodified base — a server-startup env issue unrelated to this change.)Note / follow-up
A narrow residual TOCTOU remains only if a different provider creates the same-email account in the sub-millisecond window between the pre-check and creation. It does not apply to the existing-account attack this CVE describes; fully closing it would require an atomic create-if-absent query shared with the OAuth path.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
Summary by CodeRabbit
Bug Fixes
Tests
Chores