Skip to content

get-mapbox-token Edge Function returns live Mapbox API token to any unauthenticated caller #4

Description

@tg12

Summary

The get-mapbox-token Edge Function returns the raw Mapbox secret API token to any caller without authentication. Combined with wildcard CORS and verify_jwt = false, any browser tab on any origin can retrieve the live Mapbox token.

Evidence

supabase/functions/get-mapbox-token/index.ts in full:

serve(async (req) => {
  if (req.method === "OPTIONS") {
    return new Response(null, { headers: corsHeaders });
  }

  const token = Deno.env.get("MAPBOX_TOKEN");
  if (!token) {
    return new Response(JSON.stringify({ error: "MAPBOX_TOKEN not configured" }), {
      status: 500,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }

  return new Response(JSON.stringify({ token }), {
    headers: { ...corsHeaders, "Content-Type": "application/json" },
  });
});

CORS header: "Access-Control-Allow-Origin": "*" — any origin may receive the token.
supabase/config.toml: [functions.get-mapbox-token] verify_jwt = false — no auth required.

Why this matters

A Mapbox secret token grants full API access under the owner's account, including:

  • Generating unlimited map tile requests (billable)
  • Creating and deleting datasets, tilesets, and styles
  • Potentially accessing private datasets if the token has write scopes

The token is returned as plaintext JSON to any HTTP GET or POST request, from any origin, with no credential required. Bots routinely scrape Supabase Edge Function URLs.

Attack or failure scenario

  1. Attacker sends: curl https://ldqvretuwballytbywfc.supabase.co/functions/v1/get-mapbox-token
  2. Response: {"token":"pk.eyJ..."} — live Mapbox token returned.
  3. Attacker uses the token to generate tile requests, create datasets, or exhaust the owner's Mapbox quota/billing.

Alternatively, a malicious ad or third-party script on any page embedding this app can fetch() the endpoint and exfiltrate the token cross-origin (wildcard CORS permits it).

Root cause

The function was built to avoid embedding the Mapbox token directly in the frontend bundle (a correct instinct), but the solution — a proxy endpoint that returns the token — provides no actual protection. The endpoint itself must be auth-gated to be useful as a token proxy.

Recommended fix

Option A (correct for a private app): Require a valid Supabase JWT:

  1. Remove verify_jwt = false from supabase/config.toml for this function.
  2. Frontend passes the session token in the Authorization header when calling the function.

Option B (if truly public tile access is needed): Do not proxy the secret token. Instead:

  1. Use a Mapbox temporary token scoped to read-only tile access and a specific URL restriction.
  2. Generate short-lived, scope-restricted tokens server-side and return those, never the master secret.

Option C: Use Mapbox's URL restrictions feature to limit token usage to your production domain only, and use pk.* public tokens (not sk.* secret tokens) directly in the frontend bundle.

Acceptance criteria

  • Calling /functions/v1/get-mapbox-token without a valid Supabase JWT returns 401
  • OR the function no longer returns the master Mapbox secret token — only scope-restricted, URL-restricted tokens
  • The Mapbox token currently exposed has been rotated

Suggested labels

security, bug

Priority

P0

Severity

Critical — live API token for a billable third-party service returned to any unauthenticated caller.

Confidence

Confirmed — code explicitly returns Deno.env.get("MAPBOX_TOKEN") with no auth check and verify_jwt = false.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions