{"schema_version":"1.9.0","id":"GHSA-47c3-hpmg-7j6p","published":"2026-10-09T20:56:37Z","modified":"2026-10-09T21:15:03.798871211Z","aliases":["CVE-2026-108258"],"summary":"Shiny for Python has path traversal in bookmark restore","details":"### Impact\n\nShiny for Python's bookmark-restore path accepted a client-supplied `_state_id_`\nquery-string value and joined it into the server-side bookmark directory\n(`<cwd>/shiny_bookmarks/<id>`) without validating it. A value containing `..`\npath segments, or an absolute path, could therefore cause the server to open and\nparse `input.json` and `values.json` from a directory outside the bookmark store.\n\nOn a default application the restore was attempted whenever the client supplied a\nURL query string — including when bookmarking was disabled\n(`bookmark_store=\"disable\"`) — so no opt-in configuration was required. For such\napplications the impact is an unauthenticated, server-side probe for the\nexistence and JSON-validity of attacker-chosen paths, constrained to those two\nfile names.\n\nApplications configured with `bookmark_store=\"server\"` **and** using\n`ui.input_file()` are affected more seriously. The file-input restore handler\ncopies files out of the restore directory, so the contents of an\nattacker-selected file name within an attacker-selected directory could be read\ninto the application.\n\n### Affected versions\n\n1.4.0 through 1.6.3. Bookmarking was introduced in 1.4.0; earlier releases do\nnot contain the affected code path and are not affected.\n\n### Patches\n\nFixed in **1.6.4**. Upgrade with:\n\n```\npip install --upgrade shiny\n```\n\nThe fix validates bookmark IDs against an allowlist — a single path segment\nmatching `[A-Za-z0-9_-]+`, which cannot express a path separator, a `..` parent\nreference, or an absolute path — before the ID is used to build a filesystem\npath. Bookmark IDs generated by Shiny are unaffected by this restriction.\nRestore is additionally gated on the application's `bookmark_store` setting at a\nsingle point, so it no longer runs when bookmarking is disabled, and the on-disk\nrestore path is honored only under `bookmark_store=\"server\"`.\n\n### Workarounds\n\nThere is no configuration change that mitigates this on an affected version:\nthe restore path ran even with `bookmark_store=\"disable\"`. Deployments that\ncannot upgrade immediately can strip the `_state_id_` parameter from incoming\nquery strings at a reverse proxy or load balancer, which prevents the affected\ncode path from being reached.\n\n### Credits\n\nReported by [@0xRenSec](https://github.com/0xRenSec).","affected":[{"package":{"name":"shiny","ecosystem":"PyPI","purl":"pkg:pypi/shiny"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.4.0"},{"fixed":"1.6.4"}]}],"versions":["1.4.0","1.5.0","1.5.1","1.6.0","1.6.1","1.6.2","1.6.3"],"database_specific":{"last_known_affected_version_range":"<= 1.6.3","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-47c3-hpmg-7j6p/GHSA-47c3-hpmg-7j6p.json"}}],"references":[{"type":"WEB","url":"https://github.com/posit-dev/py-shiny/security/advisories/GHSA-47c3-hpmg-7j6p"},{"type":"WEB","url":"https://github.com/posit-dev/py-shiny/commit/1d8ecb46cbc9621b7dc8812111d26692e086b376"},{"type":"PACKAGE","url":"https://github.com/posit-dev/py-shiny"},{"type":"WEB","url":"https://github.com/posit-dev/py-shiny/releases/tag/v1.6.4"}],"database_specific":{"cwe_ids":["CWE-22"],"github_reviewed":true,"github_reviewed_at":"2026-10-09T20:56:37Z","nvd_published_at":null,"severity":"MODERATE"},"severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N"}]}