Skip to content

No polygon input validation in GEE Edge Functions — unbounded vertex count and coordinate range accepted #9

Description

@tg12

Summary

No Edge Function validates the polygon coordinate input before passing it to Google Earth Engine. There are no limits on vertex count, coordinate range, or geographic area. A malicious request with thousands of coordinates, an invalid bounding box, or coordinates outside the Earth's surface will trigger expensive GEE computation attempts before failing.

Evidence

supabase/functions/analyze-field/index.ts:

const body = await req.json();
if (body.polygon) {
  const { polygon } = body;
  let coords: [number, number][];
  if (polygon.type === "Polygon" && Array.isArray(polygon.coordinates)) {
    coords = polygon.coordinates[0];          // no vertex count check
  } else if (Array.isArray(polygon) && polygon.length >= 3) {
    coords = polygon;                         // accepts any array with ≥ 3 elements
  } else {
    throw new Error("Invalid polygon");
  }
  // Immediately calls GEE with unvalidated coords:
  const token = await getGeeAccessToken();
  ...

Same pattern in gee-analytics/index.ts, gee-ndvi-tiles/index.ts, and ndvi-timeseries/index.ts. None of them check:

  • Maximum number of vertices (GEE performance degrades with complex polygons)
  • Coordinate value ranges (longitude: -180 to 180, latitude: -90 to 90)
  • Minimum or maximum polygon area (prevents trivially tiny or planet-scale requests)
  • GeoJSON ring closure (first and last coordinate must be identical)
  • Degenerate geometries (self-intersecting, zero-area polygons)

The GEE call itself sets maxPixels: { constantValue: 1000000000 } — 1 billion pixels — which is a very high limit that does not protect against resource exhaustion before the GEE server rejects the request.

Why this matters

  1. Resource exhaustion without auth: Each malformed or enormous polygon triggers a GEE OAuth token exchange, a GEE API call, and potentially seconds of GEE compute time before failing. Without auth, an attacker can flood these endpoints with arbitrarily large polygons.
  2. Error message disclosure: GEE errors are caught and re-thrown with the GEE status code and partial response text: throw new Error(\GEE compute failed (${resp.status}): ${t}`)` — these details leak into the 500 response to the caller.
  3. Unexpected behavior with degenerate geometries: A polygon with 2 points, a self-intersecting ring, or coordinates outside [-180,180]/[-90,90] will produce undefined GEE behavior that may silently return wrong results rather than an error.

Root cause

The input validation layer was skipped entirely. The assumption is that coordinates come from the frontend map drawing tool (Mapbox GL Draw), which produces valid GeoJSON. This trust boundary is invalidated the moment a caller can POST arbitrary JSON to the endpoint.

Recommended fix

Add a validation function before any GEE call:

function validatePolygon(coords: [number, number][]): void {
  if (coords.length < 4) throw new Error("Polygon must have at least 4 coordinate pairs (3 vertices + closure)");
  if (coords.length > 500) throw new Error("Polygon exceeds maximum vertex count (500)");
  for (const [lng, lat] of coords) {
    if (typeof lng !== "number" || typeof lat !== "number") throw new Error("Non-numeric coordinate");
    if (lng < -180 || lng > 180) throw new Error(`Longitude out of range: ${lng}`);
    if (lat < -90 || lat > 90) throw new Error(`Latitude out of range: ${lat}`);
  }
  // Area check: compute approximate bounding box area, reject > ~50,000 km² (country-scale)
  const lngs = coords.map(c => c[0]);
  const lats = coords.map(c => c[1]);
  const dLng = Math.max(...lngs) - Math.min(...lngs);
  const dLat = Math.max(...lats) - Math.min(...lats);
  if (dLng * dLat > 20) throw new Error("Polygon bounding box exceeds maximum allowed area");
}

Also reduce maxPixels from 1_000_000_000 to a realistic value (e.g., 100_000_000 for field-scale analysis).

Acceptance criteria

  • All GEE-calling Edge Functions validate polygon input before calling getGeeAccessToken()
  • Vertex count is capped at a documented maximum
  • Coordinate values are range-checked (lng ∈ [-180,180], lat ∈ [-90,90])
  • Requests with overly large bounding-box areas return a 400 error before any GEE API call
  • maxPixels is set to a value that reflects the maximum expected field size

Suggested labels

security, bug

Priority

P2

Severity

Medium — enables cost amplification (more targeted than the auth issue alone) and potential information disclosure via error messages. Lower severity because fixing auth mitigates anonymous abuse.

Confidence

Confirmed — all four GEE-calling functions parse and use polygon coordinates with no validation beyond checking that the input is an array with ≥ 3 elements.

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