Skip to content

Notion OAuth callback hardcoded to localhost — open redirect to attacker-controlled local server #4

@noobx123

Description

@noobx123

Severity : Medium
CVSS : 5.4 (AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)
Endpoint : https://vectorbase.dev/api/notion/callback

Notion OAuth callback hardcoded to localhost — open redirect to attacker-controlled local server

Summary

The Notion OAuth callback handler redirects users to [https://localhost:3000/...](https://localhost:3000/...) in all cases because the production app's NEXT_PUBLIC_APP_URL environment variable is set to localhost:3000 instead of https://vectorbase.dev/. Any user who initiates a Notion connection is redirected to their local machine's port 3000 after the OAuth flow, which an attacker running a local server can exploit to steal the Notion workspace name and encrypted access token appended to the redirect URL.

Steps / PoC

  1. Observe the redirect destination without any authentication:
curl -s -D - "https://vectorbase.dev/api/notion/callback?code=test&state=dGVzdA" | grep -i location

Response:

location: https://localhost:3000/dashboard/projects?notion_error=invalid_state
  1. With a valid Notion OAuth code (obtained by initiating Notion OAuth as a legitimate user), the callback executes successfully and redirects with the access token in the URL:
https://localhost:3000/dashboard/projects/<id>?notion_connected=true
  &workspace_id=<id>&workspace_name=<name>&access_token=<encrypted-token>

The encrypted Notion access token is appended to the redirect URL that goes to localhost. If the victim's browser has anything listening on port 3000 (a dev server, any local app), that server receives the token in the Referer header and request path on subsequent navigations.

  1. The access_token in the URL is encrypted using scrypt(ENCRYPTION_KEY, 'salt', 32). If the ENCRYPTION_KEY environment variable is not set in production (the code falls back to 'default-key-change-in-production'), the encrypted token is trivially decryptable, yielding a live Notion API token with full workspace access.

Root cause:

// src/app/api/notion/callback/route.ts
const appUrl = process.env.NEXT_PUBLIC_APP_URL  // = "localhost:3000" in production
return NextResponse.redirect(
  `${appUrl}/dashboard/projects/${projectId}?notion_connected=true&access_token=${encryptedToken}`
)

Impact

Users who connect Notion are redirected to localhost after completing OAuth; any service running on their local port 3000 receives the Notion access token, enabling an attacker with local server access (or a malicious local app) to steal and decrypt it.

Fix

  1. Set NEXT_PUBLIC_APP_URL=https://vectorbase.dev/ in the production deployment environment.
  2. Never include sensitive tokens in redirect URLs; store them server-side and provide a session-scoped reference instead.
  3. Set ENCRYPTION_KEY to a cryptographically random 32-byte value and use a unique per-token IV/salt rather than the static string 'salt'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions