Skip to content

Configure CORS on organization S3 buckets so the platform's "download all" buttons work #642

Description

@fe51

Objective

The platform frontend has three image-download features (see AlertImages.tsx):

Mode Method Needs CORS?
1 — download current image native <a download> No (browser navigation, no XHR)
2 — download all as ZIP fetch(detection.url) → JSZip Yes
3 — download all + bounding boxes fetch(detection.url) → canvas Yes

Modes 2 and 3 use native fetch() against the presigned S3 URLs. Because these are
cross-origin requests to the object storage host, the browser requires the S3 response to
carry an Access-Control-Allow-Origin header. Today our buckets are created with no CORS
configuration
(S3Service.create_bucket only calls create_bucket, nothing else), so both
buttons fail.

Proposed change

  1. Add a new config setting S3_CORS_ORIGINS (comma-separated → List[str]) listing the
    allowed frontend origins (prod, dev, localhost dev). Default ["*"] for local/dev.
  2. Extend S3Service.create_bucket to apply a CORS policy via put_bucket_cors right after
    the bucket is created:
    • AllowedMethods: ["GET", "HEAD"] (browser only reads images; uploads are server-side)
    • AllowedOrigins: settings.S3_CORS_ORIGINS
    • AllowedHeaders: ["*"], ExposeHeaders: ["Content-Length", "Content-Type"],
      MaxAgeSeconds: 3000
  3. Add a test (LocalStack) asserting get_bucket_cors returns the expected rules after
    creation.

⚠️ Deployed environments -> retroactive backfill required

create_bucket only runs at organization creation, so this change applies to new orgs
only
. Existing buckets on the cloud provider will keep having no CORS policy. Once the
mechanism is validated, we must run a one-off retroactive script that applies
put_bucket_cors to every already-existing bucket. Track this as a follow-up step before
considering the issue done in prod.

[ ] Open point to confirm

Most S3-compatible object storage providers support the standard CORS API, but behavior
varies. We should verify with a get_bucket_cors read against a real bucket that the policy
is actually stored (and that we are hitting the provider's S3-compatible endpoint, not a
legacy Swift-style one).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions