fix: stop leaking connector OAuth session tokens to other origins

The connector OAuth popup posted the session token to window.opener with
a '*' target origin, so any page that opened the popup received it. An
attacker with an account on a multi-user deployment could start a flow for
their own pending session, get a victim to finish the provider consent, and
receive a token backed by the victim's Drive/SharePoint/Confluence tokens.

- Post popup results only to allowed frontend origins: the callback origin,
  OIDC_FRONTEND_URL, the new CONNECTOR_ALLOWED_ORIGINS, and localhost:5173
  when the callback runs on a loopback host.
- Render the success page from the callback itself so the token never
  appears in a URL; callback-status ignores session_token/user_email params.
- ConnectorAuth accepts messages only from the popup it opened, on the
  callback origin reported by /api/connectors/auth.
- /api/connectors/disconnect requires auth and only deletes the caller's
  session.
- /api/connectors/sync and /api/remote reject session tokens the caller
  does not own.

Fixes #2766
This commit is contained in:
Alex committed 2026-09-14 21:46:09 +01:00
1 parent d121eec2f5
commit 41b3afed14
14 files changed
+575 -87

No files matched your search

+10
View File
@@ -24,6 +24,7 @@ from docsgpt.security.zip_archive import (
ZipExtractionError,
ZipExtractionLimits,
)
from docsgpt.storage.db.repositories.connector_sessions import ConnectorSessionsRepository
from docsgpt.storage.db.repositories.idempotency import IdempotencyRepository
from docsgpt.storage.db.repositories.sources import SourcesRepository
from docsgpt.storage.db.source_config import SourceConfig
@@ -496,6 +497,15 @@ class UploadRemote(Resource):
),
400,
)
with db_readonly() as conn:
connector_session = ConnectorSessionsRepository(conn).get_by_session_token(session_token)
if not connector_session or connector_session.get("user_id") != user:
if scoped_key:
_release_claim(scoped_key)
return make_response(
jsonify({"success": False, "error": "Invalid or unauthorized session"}),
401,
)
# Process file_ids
file_ids = config.get("file_ids", [])