Skip to content

Wildcard CORS (Access-Control-Allow-Origin: *) on all 8 Edge Functions enables cross-origin credential exfiltration #8

@tg12

Description

@tg12

Summary

All 8 Edge Functions set Access-Control-Allow-Origin: *, permitting cross-origin requests from any domain. Because several functions are also unauthenticated (no JWT required) and return credentials (Mapbox token, GEE access token), wildcard CORS elevates those vulnerabilities from "server-side attack" to "browser-based cross-site exfiltration."

Evidence

All eight functions share the same CORS header block:

// supabase/functions/analyze-field/index.ts
// supabase/functions/crop-planning/index.ts
// supabase/functions/gee-analytics/index.ts
// supabase/functions/gee-ndvi-tiles/index.ts
// supabase/functions/get-mapbox-token/index.ts
// supabase/functions/keepalive/index.ts
// supabase/functions/ndvi-timeseries/index.ts
// supabase/functions/soil-data/index.ts
const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, ...",
};

The wildcard origin is returned on both preflight OPTIONS responses and all actual responses.

Why this matters

With Access-Control-Allow-Origin: * and verify_jwt = false:

  • Any web page (malicious ad, XSS payload, third-party script) can call these endpoints from a user's browser and read the response.
  • For get-mapbox-token: any cross-origin page can fetch {"token":"..."} and exfiltrate the Mapbox key to an attacker's server.
  • For gee-ndvi-tiles: any cross-origin page can fetch {"tileUrl":"...", "token":"ya29..."} and exfiltrate the GEE OAuth token.
  • For AI functions: any cross-origin page can trigger Lovable AI gateway calls using the owner's quota.

Note: wildcard CORS with credentials: 'include' is blocked by the browser spec, but these functions don't use cookies — they return tokens in response bodies, which wildcard CORS does not protect.

Root cause

Lovable's scaffold generates wildcard CORS headers for Edge Functions to simplify development and preview environments. This default was never tightened for production deployment.

Recommended fix

Restrict Access-Control-Allow-Origin to the production origin:

const ALLOWED_ORIGIN = Deno.env.get("ALLOWED_ORIGIN") ?? "https://your-production-domain.com";

const corsHeaders = {
  "Access-Control-Allow-Origin": ALLOWED_ORIGIN,
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
  "Vary": "Origin",
};

For local development, set ALLOWED_ORIGIN=http://localhost:5173 in local Supabase config. For production, set ALLOWED_ORIGIN=https://<production-domain> in Supabase project secrets.

If multiple origins must be allowed (e.g., preview deployments), implement an allowlist:

const ALLOWED_ORIGINS = new Set([
  "https://production.example.com",
  "https://staging.example.com",
]);
const origin = req.headers.get("Origin") ?? "";
const corsOrigin = ALLOWED_ORIGINS.has(origin) ? origin : "null";

Acceptance criteria

  • No Edge Function returns Access-Control-Allow-Origin: * in production
  • CORS origin is restricted to the application's production domain
  • Vary: Origin header is set where dynamic origin reflection is used
  • CORS restriction is enforced via environment variable, not hardcoded string

Suggested labels

security, bug

Priority

P2

Severity

Medium in isolation; escalates the severity of the token exposure issues (P0) by enabling browser-based cross-origin exfiltration of API credentials.

Confidence

Confirmed — all 8 functions hardcode "Access-Control-Allow-Origin": "*".

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