gradio
29aa48fe - Fix deep links on Windows (#13868)

Commit
38 days ago
Fix deep links on Windows (#13868) * Fix deep links on Windows and keep an unusable one from breaking the app `load_deep_link` built the state.json lookup with `Path`, so on Windows the relative path reached `safe_join` as `deep_links\<id>\state.json`. `safe_join` rejects the OS separator, so every deep link raised `HTTPException(403)` — which `load_deep_link` did not catch, turning `GET /config?deep_link=...` into an error response instead of a config. Build the path with forward slashes. A missing or unreadable link had the same problem: `routes_safe_join` raises `HTTPException(404)` for a path that does not exist, so the `deep_link_state = "invalid"` branch was unreachable and the whole config request failed. Catch `HTTPException` too, and fall back to the components the app would normally load, so the frontend can report the bad link with a toast over a working app. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Only accept a deep link that looks like a deep link The previous fix built the lookup path as a POSIX string so that `safe_join` would stop rejecting it on Windows, and relied on `safe_join` to normalise away any traversal. It does not: `posixpath.normpath` collapses `deep_links/../planted/state.json` down to `planted/state.json`, which `safe_join` accepts, so `?deep_link=../x` read any `state.json` sitting next to `deep_links/` -- i.e. anywhere in the upload directory -- and returned its contents as the app's components. The `../escaping` test passed only because that file did not exist. A deep link is the URL-safe base64 digest from `create_url_safe_hash`, so validate it against that alphabet before building a path. That keeps separators and `..` out of the lookup entirely rather than depending on how they normalise, and fixes the Windows case as a side effect. `safe_join` stays as a second line of defense. Falling back to `config["components"]` on an unusable link handed out the live `blocks.config` list. `/config` assigns it over its own copy, and `update_root_in_config` rewrites file urls in place, so a single `GET /config?deep_link=anything-invalid` baked that request's root url into the config for every later response -- turning per-request Host reflection into persistent, cross-user rewriting of every asset url. Return `None` instead and let the caller serve the copy it already has. Also require authentication to create a deep link, so an unauthenticated request cannot snapshot a session's state to disk on an app with auth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Confine a deep link to its own storage root Anchor `safe_join` at `<upload dir>/deep_links` instead of at the upload directory, so the base it refuses to escape is the one that actually matters. With the upload directory as the base, `deep_links/../<hash>/state.json` normalises to `<hash>/state.json`, which escapes nothing and is accepted -- and `/gradio_api/upload` lets anyone store a file named `state.json` and hands back the directory it landed in. So the previous commit's token validation was the only thing standing between an uploaded file and trusted component config, which is executable: an `html` component's `js_on_load` is run through `new Function`, so this was same-origin script execution on any public app, not just a file read. Both controls now block it independently. Also stop swallowing every `HTTPException`. Only 403/404 from `routes_safe_join` mean the link does not name a readable file; anything else is unexpected and should not quietly become an invalid link. Test the real chain rather than a hand-placed file: upload a crafted `state.json` through `/gradio_api/upload` and ask for it back as `?deep_link=../<hash>`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Trim the comments and drop the hand-placed traversal test Keep only the comments that explain something the code cannot: where the token alphabet comes from, why the join is based at `deep_links` rather than the upload directory, why the relative path is a POSIX string, and why the components must not be aliased from the live config. `test_deep_link_cannot_read_a_file_outside_its_own_directory` planted a `state.json` by hand to stand in for an upload. The upload test covers the same ground through the route an attacker would actually use, so only its path aliases are worth keeping; they move to the fallback test, which needs no file on disk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Abubakar Abid <islamrealm@gmail.com>
Author
Parents
Loading