# \[BUG\] Embedded download button does not work if have PublicSpace -\> Private Space

**URL:** <https://discuss.huggingface.co/t/bug-embedded-download-button-does-not-work-if-have-publicspace-private-space/171838>\
**Category:** Spaces\
**Created:** [December 22, 2025, 2:58pm UTC](https://discuss.huggingface.co/t/bug-embedded-download-button-does-not-work-if-have-publicspace-private-space/171838 "2025-12-22T14:58:25Z")\
**Posts on this page:** 1\
**Showing post:** 4

<div class="post-metadata">

**Author:** ![John6666](https://sea2.discourse-cdn.com/hellohellohello/user_avatar/discuss.huggingface.co/john6666/32/27664_2.png) [@John6666](https://discuss.huggingface.co/u/John6666)\
**Post date:** [December 23, 2025, 4:52am UTC](https://discuss.huggingface.co/t/bug-embedded-download-button-does-not-work-if-have-publicspace-private-space/171838/4 "2025-12-23T04:52:47Z")

</div>

`iframe` one?

* * *

This behavior is expected once you combine three things: **(1) Hugging Face Spaces are iframe-embedded**, **(2) your “Download” UI is ultimately an HTML link**, and **(3) the image URL becomes effectively cross-origin because it is served through a proxy chain**.

Your URL shows the whole chain:

```auto
publicspace.hf.space
  /gradio_api/proxy= https://privatespace.hf.space/gradio_api/file= https://privatespace.hf.space/gradio_api/file=/tmp/gradio/.../image.webp

```

That “proxy= … file= … file=” nesting is the key smell: the download link is not a clean “same-origin downloadable file”. It is a proxied, rewritten asset URL.

Below are the main causes, how to verify each one quickly, and the fixes that actually stick.

* * *

## Background: why this happens on Hugging Face in particular

### Spaces are embedded in an iframe on the Hugging Face page

When you open a Space via `https://huggingface.co/spaces/<user>/<space>`, Hugging Face embeds the real app (the `https://*.hf.space/` origin) inside an iframe. Hugging Face documents this explicitly. ([Hugging Face](https://huggingface.co/docs/hub/en/spaces-handle-url-parameters "How to handle URL parameters in Spaces"))

So even before you add a private Space, you are already in an iframe context.

### Embedding requires a public Space

Hugging Face’s embed doc states that to embed a Space, it must be public. That is why many people do the “public wrapper Space → private Space” pattern. ([Hugging Face](https://huggingface.co/docs/hub/en/spaces-embed "Embed your Space in another website"))

* * *

## Cause 1: the browser ignores `<a download>` for cross-origin URLs

Most “download icons” in web UIs are implemented as an `<a href="...">` link with a `download` attribute, or equivalent logic.

Modern browsers intentionally **ignore** the download attribute in cross-origin situations to prevent data leakage and abuse. Chrome formalized this in Chrome 65. ([Chrome for Developers](https://developer.chrome.com/blog/chrome-65-deprecations "Deprecations and removals in Chrome 65 | Blog"))

So if the final resource is treated as cross-origin (and in your case it is, because it ultimately points to the private Space origin or a proxy of it), the browser is allowed to do the “safe” thing: **navigate to the image** (preview it) instead of downloading.

This is also exactly what the Gradio team calls out in a related bug: download buttons fail in iframes because the embedded app and the parent have different origins and `<a download>` does not work across them. ([GitHub](https://github.com/gradio-app/gradio/issues/6722 "Download buttons do not work inside iframes · Issue #6722"))

### Why your setup triggers cross-origin

- Public Space origin: `publicspace.hf.space`
- Private Space origin: `privatespace.hf.space`
- Your download URL contains a proxy-to-private chain.

Different hostnames means different origins. Same-origin policy is strict about this. ([MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy "Same-origin policy - Security - MDN Web Docs - Mozilla"))

* * *

## Cause 2: proxy/file routing returns “inline image” behavior

Even if the UI tries to force download, the response often comes back as:

- `Content-Type: image/webp`
- no `Content-Disposition: attachment`

Browsers will typically render images inline in a new tab when you navigate to such a URL.

In a public→private setup, it is common that the _outer_ proxy route behaves like a normal image fetch, not like a “download endpoint”.

This category of “proxy breaks file access / download” issues exists in Gradio generally (proxy environments change URL handling and file access rules). ([GitHub](https://github.com/gradio-app/gradio/issues/10726 "Video component can't download URL behind a proxy"))

* * *

## Cause 3: Gradio `gr.load()` + private Space outputs are a known rough edge

Multiple reports match the broader pattern: the UI loads, inference runs, but **outputs that are files/images fail or behave oddly** when the loaded Space is private.

Examples:

- “loading a private space from a public one… interface loads, output retrieval errors” ([GitHub](https://github.com/gradio-app/gradio/issues/6509 "Loading a private space on huggingface from a public one"))
- “outputs from private space result in a 404” ([Hugging Face Forums](https://discuss.huggingface.co/t/gradio-load-failing-to-load-outputs-from-private-space/80357 "Gradio.load() failing to load outputs from private space"))
- “gr.load private space… images don’t work” ([Hugging Face Forums](https://discuss.huggingface.co/t/gr-load-private-space/70385 "Gr.load private space"))
- “public space loads private space… fails to load images” ([Hugging Face Forums](https://discuss.huggingface.co/t/accessing-data-in-a-private-space-from-a-public-space/49741 "Accessing data in a private space from a public space"))

Your symptom is slightly different (preview instead of download), but it is the same underlying theme: **file outputs are the fragile part** when you cross Space visibility boundaries.

* * *

## Cause 4: the “double file=” URL is probably a rewrite bug (or double-wrapping)

Your URL includes:

- `.../file=https://privatespace.../file=/tmp/gradio/...`

That means something treated an already-constructed file URL as a string and then wrapped it again.

This can happen when:

- the loaded app returns a URL string instead of a file object/path
- the wrapper app (or Gradio front-end) tries to “make it accessible” and prepends another file/proxy handler

You can usually confirm this by inspecting the JSON returned by the predict call and seeing whether the “image” is a structured file object vs a raw URL string.

* * *

## How to confirm the exact cause in DevTools

Do these three checks once. They remove guesswork.

### 1) Inspect the element behind the download icon

- Right click the download icon → Inspect.
- Look for an `<a>` tag and see its `href`.
- If it’s your long proxy URL, you already know the browser is likely to ignore “download” for cross-origin. ([Chrome for Developers](https://developer.chrome.com/blog/chrome-65-deprecations "Deprecations and removals in Chrome 65 | Blog"))

### 2) Network tab: compare headers

Click download, then inspect the response headers for that request.

If you do **not** see something like `Content-Disposition: attachment`, the browser will happily preview it.

(And in cross-origin situations, even having `download` on the link often won’t help.)

### 3) Try the “stripped to first file=” URL and compare

When you manually strip to the first `file=`, you bypass one wrapper. If the behavior changes, that is consistent with “outer proxy URL is the problem”.

* * *

## Solutions that work reliably

I’m listing these in the order that tends to be most robust on Spaces.

### Solution 1 (recommended): re-host the output file on the public Space, then download locally

Instead of trying to download the private Space’s `/tmp/gradio/...` file through a proxy URL, do this:

1. Public Space calls private Space (server-to-server) to generate.
2. Public Space downloads the bytes (using its token).
3. Public Space exposes the result as a **local file** and uses `gr.DownloadButton`.

Why it works:

- The downloadable URL becomes same-origin (`publicspace.hf.space`).
- You avoid cross-origin `<a download>` restrictions. ([Chrome for Developers](https://developer.chrome.com/blog/chrome-65-deprecations "Deprecations and removals in Chrome 65 | Blog"))
- You avoid nested `proxy=...file=...file=...` URLs.

Gradio explicitly provides `DownloadButton` for this use case. ([Gradio](https://www.gradio.app/docs/gradio/downloadbutton "DownloadButton"))

Also, use Gradio’s Python client to call remote apps cleanly. ([Gradio](https://www.gradio.app/docs/python-client/client "Gradio Python Client - Class Docs"))

### Solution 2: add a custom “download endpoint” that sets attachment headers

If you can add a small FastAPI route (common in Docker Spaces), you can make:

`/download?path=<id>`

and respond with `Content-Disposition: attachment`.

This approach avoids relying on the browser’s cross-origin download attribute behavior. Chrome’s change is specifically about ignoring `<a download>` cross-origin, not about forbidding downloads when servers mark attachments. ([Chrome for Developers](https://developer.chrome.com/blog/chrome-65-deprecations "Deprecations and removals in Chrome 65 | Blog"))

### Solution 3: return bytes (or base64) from the private Space instead of a temp file URL

If you control the private Space app:

- return the image as bytes (or base64)
- have the public Space write it to disk
- then offer it via `DownloadButton`

This avoids `/gradio_api/file=/tmp/gradio/...` entirely.

### Solution 4: if you embed the Space on your own site, ensure iframe sandbox allows downloads

If you embed `*.hf.space` in an iframe on your own page and you set a restrictive `sandbox`, downloads can be blocked unless you allow them. This is a separate but common pitfall.

(If you are only using the Hugging Face Space page, you typically do not control this, so Solutions 1–3 are the practical ones.)

* * *

## Pitfalls to avoid

### Pitfall A: depending on `/tmp/gradio/...` URLs across machines or replicas

Even when not private, `/tmp` is ephemeral and can break across restarts. Keep downloads stable by re-hosting or regenerating.

### Pitfall B: assuming `gr.File` or “raw URL string” outputs behave the same across versions

Gradio’s file access and serving rules tightened over time, and there are reports of file serving changes and breakages behind proxies and in newer versions. ([Gradio](https://www.gradio.app/guides/file-access "Security and File Access"))

### Pitfall C: “public wrapper loads private UI” is convenient but fragile for outputs

The volume of threads on this pattern should be treated as a warning sign. ([Hugging Face Forums](https://discuss.huggingface.co/t/gradio-load-failing-to-load-outputs-from-private-space/80357 "Gradio.load() failing to load outputs from private space"))

* * *

## If you want to file a good upstream bug report

Include:

- the exact URL shape showing `proxy=...file=...file=...`

- Gradio version on both Spaces

- DevTools screenshots of:

- a minimal pair of Spaces: one public wrapper, one private generator

Reference the existing Gradio iframe download issue as related context. ([GitHub](https://github.com/gradio-app/gradio/issues/6722 "Download buttons do not work inside iframes · Issue #6722"))

* * *

## Summary bullets

- Root cause is usually **cross-origin download restrictions** plus **proxy-wrapped file URLs**. ([Chrome for Developers](https://developer.chrome.com/blog/chrome-65-deprecations "Deprecations and removals in Chrome 65 | Blog"))
- Hugging Face adds an iframe layer and public→private introduces multiple origins. ([Hugging Face](https://huggingface.co/docs/hub/en/spaces-handle-url-parameters "How to handle URL parameters in Spaces"))
- Best fix is to **fetch the file server-side in the public Space and serve it locally via `DownloadButton`**. ([Gradio](https://www.gradio.app/docs/gradio/downloadbutton "DownloadButton"))
- The “double `file=`” indicates **double URL rewriting** , often from returning URLs instead of file objects.

---

_[View the full topic](https://discuss.huggingface.co/t/bug-embedded-download-button-does-not-work-if-have-publicspace-private-space/171838)._
