Skip to content

Worklogs export download links use http:// even with MINIO_ENDPOINT_SSL=1 set #9550

Open

Description

@jordan-tryon

Related to #5827, same root issue but specific to the Worklogs/Timesheets export feature.

We're self-hosting Plane Commercial (Docker Compose via prime-cli), behind Cloudflare Tunnel so TLS ends at Cloudflare and the internal proxy traffic is plain HTTP. Tested this on both v2.5.3 and v3.0.1, same result on both.

Every download link from Worklogs export (Project Settings > Worklogs > Download, plus the Previous Downloads list) comes back as an http:// presigned URL, like:

http:///uploads/.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&...

Since the site itself is https, Chrome silently blocks these as insecure downloads. No error, no toast, the click just does nothing. Confirmed the file itself is fine, curl -L on the same url works and pulls the CSV down no problem, so it's really just the scheme on the link that's wrong.

We set MINIO_ENDPOINT_SSL=1 and AWS_S3_ENDPOINT_URL=https://, which per storage.py is supposed to force https on generated presigned urls when behind a proxy like ours. Verified both vars are actually set inside the api and worker containers, and the app confirms it can reach the bucket fine through the new url on startup (Checking bucket... Bucket 'uploads' exists.).

Everything else that goes through S3Storage seems to respect this fine based on the code, but Worklogs export specifically doesn't. Freshly generated exports (checked via server request logs, not cached) still come back http:// even with the fix applied and containers freshly restarted. Seems like Worklogs/Timesheets export must be running separate code from the normal S3Storage.generate_presigned_url path, maybe since it's an enterprise only feature.

Anyone know if Worklogs export url generation is a separate code path, and if so can it get the same SSL override support?

Happy to share more logs if useful.

Activity

  1. Hayyan612 commented on Sep 16, 2026

    @Hayyan612

    Your hunch about this being a separate code path is right. Traced it, and the reason everything else respects MINIO_ENDPOINT_SSL is that everything else goes through S3Storage, which checks the flag directly at apps/api/plane/settings/storage.py:41:

    if os.environ.get("MINIO_ENDPOINT_SSL") == "1":
        endpoint_protocol = "https"
    else:
        endpoint_protocol = request.scheme if request else "http"

    Export never touches S3Storage. It builds its own boto3 client at apps/api/plane/bgtasks/export_task.py:65-73:

    presign_s3 = boto3.client(
        "s3",
        endpoint_url=(
            f"{settings.AWS_S3_URL_PROTOCOL}//{str(settings.AWS_S3_CUSTOM_DOMAIN).replace('/uploads', '')}/"
        ),
        ...
    )

    and AWS_S3_URL_PROTOCOL comes from the scheme of WEB_URL, not from MINIO_ENDPOINT_SSL and not from AWS_S3_ENDPOINT_URL. apps/api/plane/settings/common.py:313-316:

    if AWS_S3_ENDPOINT_URL and USE_MINIO:
        parsed_url = urlparse(os.environ.get("WEB_URL", "http://localhost"))
        AWS_S3_CUSTOM_DOMAIN = f"{parsed_url.netloc}/{AWS_STORAGE_BUCKET_NAME}"
        AWS_S3_URL_PROTOCOL = f"{parsed_url.scheme}:"

    That is the only definition and the only use of AWS_S3_URL_PROTOCOL in the repo, which is what makes this a one-line problem.

    The URL you pasted confirms the path. .replace('/uploads', '') strips the bucket back off the custom domain leaving http://<domain>/, and boto3 then appends /<bucket>/<key>, producing exactly http://<domain>/uploads/<uuid>.csv?X-Amz-.... The Worklogs view itself is not in this repo, but it clearly lands on the same settings pair.

    Worth noting the default self-host config guarantees this: deployments/cli/community/variables.env:15 hardcodes WEB_URL=http://${APP_DOMAIN}. Anyone terminating TLS at a proxy gets http: here no matter what they set MINIO_ENDPOINT_SSL or AWS_S3_ENDPOINT_URL to. Setting WEB_URL=https://<domain> should work around it for you in the meantime.

    Fix is to make this line agree with the check storage.py already makes:

    AWS_S3_URL_PROTOCOL = "https:" if os.environ.get("MINIO_ENDPOINT_SSL") == "1" else f"{parsed_url.scheme}:"

    Ran the settings logic in isolation across the realistic configurations to check nothing else moves:

    config current patched
    TLS at proxy, internal http, MINIO_ENDPOINT_SSL=1 http://plane.example.com/ https://plane.example.com/
    compose default, flag unset http://plane.example.com/ unchanged
    WEB_URL already https https://plane.example.com/ unchanged
    plain http deploy, MINIO_ENDPOINT_SSL=0 http://192.168.1.50/ unchanged

    Only someone who explicitly asked for SSL sees a change.

    Two caveats. ExporterHistory.url is a stored URLField written once at export_task.py:118, so existing rows keep their old links and only new exports come back correct. And this looks like the same root cause as #5827, though I cannot test the images and attachments surface to confirm.

    There is already a # TODO: Change the upload_to_s3 function to use the new storage method at export_task.py:41. Routing export through S3Storage is the proper fix and would delete this whole class of divergence, but it is a much bigger change, so I have kept this to the one line that makes the two paths agree. Opening a PR now, happy to take it the other way instead if maintainers prefer.

  2. added a commit that references this issue on Sep 16, 2026
    c2e873b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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