{"id":"CVE-2026-48797","kind":"vulnerability","aliases":["CVE-2026-48797","GHSA-f65r-h4g3-3h9h"],"related":[],"title":"Backpropagate: backprop ui --auth and backprop ui --share do not enforce authentication","description":"## Summary\n\nIn `backpropagate >= 1.1.0`, the optional Reflex web UI (`pip install backpropagate[ui]`, launched via `backprop ui`) exposes a training control plane: dataset upload, model load, training start/stop, multi-run orchestration, GGUF export, and HuggingFace Hub push.\n\nThe CLI accepts two operator-facing flags intended as security controls:\n\n- `--auth user:pass` — documented as \"require HTTP Basic authentication on every request to the UI.\"\n- `--share` — documented as \"expose the UI on a public address; requires `--auth`.\"\n\nWhen `--auth user:pass` is passed, the CLI prints `Auth: enabled (user: <username>)` to confirm to the operator that authentication is active, then exports `BACKPROPAGATE_UI_AUTH=user:pass` to the subprocess that launches the Reflex backend.\n\n**The Reflex backend (`backpropagate/ui_app/**`) never reads `BACKPROPAGATE_UI_AUTH`.** No authentication middleware is registered. No request-level guard runs. No WebSocket upgrade guard runs. Any client that reaches the bound port — local or remote, depending on whether `--share` is used — has full UI access.\n\nAn inline comment at `backpropagate/cli.py:1217-1218` in the v1.1.0 source documents the gap: *\"For Phase 1 the variable is exported but Reflex doesn't read it yet.\"* This comment was internal-facing; the user-facing documentation (README, CHANGELOG, SHIP_GATE) advertised the contract as enforced.\n\nThis advisory is filed primarily because the runtime contradicted an operator-facing security claim. Code-only bugs of comparable shape (auth check missing entirely from a path) would already warrant disclosure; the additional false-promise dimension raises the severity.\n\n## Impact\n\nAn attacker who reaches the bound port can:\n\n- **Read uploaded datasets** rendered in the UI preview, including content of any JSONL/CSV/TXT file the legitimate operator has uploaded for fine-tuning.\n- **Trigger arbitrary training runs** against any base model the operator has installed locally or that can be downloaded from HuggingFace.\n- **Trigger HuggingFace Hub pushes** to repositories named via the UI input (subject to the operator's local HF token's scope — typically all repos owned by the operator).\n- **Cause disk-fill DoS** via the `rx.upload` endpoint (no size cap, no extension filter, no per-session count cap in v1.1.0 / v1.1.1).\n- **Read model paths** (`source_model_path`, `dataset_path`, `model`, `uploaded_path`) which are user-supplied and bypass the `safe_path()` helper that lives in `backpropagate/ui_security.py` (path validation is dead code on the Reflex surface in v1.1.0 / v1.1.1).\n\nThe combination of unauthenticated training control, HF push target spoofing, and path-input traversal makes the affected endpoint suitable for both data exfiltration (reading uploaded training data) and supply-chain attacks (pushing tampered model weights to the operator's HF account).\n\nThe local-only default (no `--share`) reduces exposure to a host-local attacker. The `--share` flag is documented as a \"public URL\" feature; operators who used `--share --auth user:pass` had no warning that the auth half was inert.\n\n## Patches\n\nFixed in **v1.2.0** (released 2026-05-23). The patch implements real ASGI middleware via `rx.App(api_transformer=basic_auth_transformer)` that gates HTTP routes AND the `/_event` WebSocket upgrade. Four modes (`no_auth_local_only` / `token_auto` / `explicit_creds` / `production`), HMAC-signed cookie validated PRE-`websocket.accept()`, Host + Origin allowlists. The middleware ships alongside a 4-layer defense in depth at the cli.py / ui_app/app.py / rxconfig.py / env-strip surfaces so direct `python -m reflex run` invocations (bypassing the CLI guard) also enforce authentication.\n\nUpgrade with:\n\n- pip: `pip install --upgrade backpropagate`\n- npm: `npm install -g @mcptoolshop/backpropagate@latest`\n\nFull release notes: https://github.com/mcp-tool-shop-org/backpropagate/blob/main/CHANGELOG.md#120---2026-05-23\n\n## Workarounds\n\nIf users cannot upgrade immediately:\n\n1. **Do not pass `--auth` or `--share` to `backprop ui`.** Run the UI with no flags (`backprop ui`); it will bind to `localhost` and accept any client that can reach `127.0.0.1`.\n2. **For remote access, use SSH port-forwarding** instead of `--share`:\n   ```\n   # On the client:\n   ssh -L 7860:localhost:7860 <training-host>\n   # On the server:\n   backprop ui            # no --share\n   # Then open http://localhost:7860 in your local browser.\n   ```\n   SSH provides the authentication layer the Reflex UI did not.\n3. **Audit existing deployments.** If any host running `backpropagate >= 1.1.0` has previously been launched with `--share`, treat any uploaded training data, model paths, or HF push targets visible in that UI session as potentially exposed. Re-issue HF tokens that have been in use during such sessions.\n\n**Binary distribution gap.** Standalone binaries (Windows .exe / macOS .app via PyInstaller) failed to build for v1.2.0 and will land in a follow-up patch release. In the interim, users who relied on the v1.1.x binary distribution should install the patched version via `pip install backpropagate==1.2.0` to receive the auth-bypass fix. The v1.2.0 PyPI package and `@mcptoolshop/backpropagate@1.2.0` npm package both carry the patched code.\n\n## Credit\n\nDiscovered by the dogfood-swarm Stage A audit on 2026-05-22 (finding ID `FRONTEND-A-001`, classified CRITICAL). The audit also surfaced contradicting documentation in CHANGELOG / SHIP_GATE / README; those were corrected in v1.2.0 alongside the runtime fix.","summary":"The optional Backpropagate web UI ignores the --auth and --share flags, so the Reflex backend never checks credentials. Anyone who can reach the bound port can read uploaded training datasets, start training runs, and trigger Hugging Face Hub pushes.","severity":"critical","cvss":9.3,"epss":0.00324,"exploited":false,"exploited_at":null,"products":[{"name":"backpropagate","fixed":["1.2.0"],"affected":">= 1.1.0, < 1.2.0","ecosystem":"pypi"},{"name":"@mcptoolshop/backpropagate","fixed":["1.2.0"],"affected":">= 1.1.0, < 1.2.0","ecosystem":"npm"}],"ecosystems":["npm","pypi"],"tags":["cwe-1295","cwe-358","cwe-862","huggingface","ml-training","npm","package","python","self-hosted"],"not_affected_if":["cli-only-use","ui-not-installed","version-1.2.0-or-later"],"how_to_check":"- Run `pip show backpropagate` or `npm ls @mcptoolshop/backpropagate` and compare the version against `1.2.0`.\n- Look for `backprop ui` launched with `--share` or `--auth` in service files, shell history or process lists.\n- Check whether port 7860 is reachable from other hosts.","fix":"- Upgrade the PyPI package to `backpropagate==1.2.0` or later (`pip install --upgrade backpropagate`).\n- Upgrade the npm package to `@mcptoolshop/backpropagate@1.2.0` or later (`npm install -g @mcptoolshop/backpropagate@latest`).\n- Until upgraded, run `backprop ui` without `--auth` or `--share` and reach it over SSH port-forwarding.\n- Rotate any Hugging Face tokens used in sessions that were started with `--share`.","status":"open","published_at":"2026-06-17T13:20:43+00:00","first_seen_at":"2026-10-27T16:20:27+00:00","triaged_at":"2026-10-27T16:21:29+00:00","updated_at":"2026-10-27T16:33:07+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-27T16:21:29Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade the PyPI package to `backpropagate==1.2.0` or later (`pip install --upgrade backpropagate`).\n- Upgrade the npm package to `@mcptoolshop/backpropagate@1.2.0` or later (`npm install -g @mcptoolshop/backpropagate@latest`).\n- Until upgraded, run `backprop ui` without `--auth` or `--share` and reach it over SSH port-forwarding.\n- Rotate any Hugging Face tokens used in sessions that were started with `--share`.","reach":"network","title":"Backpropagate: web UI accepts unauthenticated training and dataset access despite --auth","impact":"high","summary":"The optional Backpropagate web UI ignores the --auth and --share flags, so the Reflex backend never checks credentials. Anyone who can reach the bound port can read uploaded training datasets, start training runs, and trigger Hugging Face Hub pushes.","category":"package","nicknames":[],"who_cares":["python","npm","ml-training","huggingface","self-hosted"],"confidence":0.85,"needs_auth":"no","worst_case":"Attacker reads training data, runs training, and pushes model weights to HF repos.","analyst_note":"","how_to_check":"- Run `pip show backpropagate` or `npm ls @mcptoolshop/backpropagate` and compare the version against `1.2.0`.\n- Look for `backprop ui` launched with `--share` or `--auth` in service files, shell history or process lists.\n- Check whether port 7860 is reachable from other hosts.","needs_analyst":false,"not_affected_if":["cli-only-use","version-1.2.0-or-later","ui-not-installed"],"who_is_affected":"Teams running Backpropagate 1.1.0 up to but not including 1.2.0 with the optional UI installed (`backpropagate[ui]`) are exposed. Exposure is highest when `--share` was used, since the UI then listens on a public address. Hosts that only ever ran the CLI for training, or that run 1.2.0 or later, are not affected.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"critical"},"cost_usd":0.000921,"input_tokens":5445,"output_tokens":752,"severity_before":"critical"},"analyst_status":null,"analyst_note":null,"search":"'-05':561C '-1218':245C '-1295':27B '-2026':14A,22A '-23':562C '-358':29B '-48797':15A,23A '-862':31B '/_event':582C '/mcp-tool-shop-org/backpropagate/blob/main/changelog.md#120---2026-05-23':658C '1':255C,665C '1.1.0':84C '1217':244C '2026':560C '3h9h':20A '4':611C 'accept':121C 'access':238C 'account':513C 'activ':178C 'addit':321C 'address':155C 'advertis':282C 'advisori':288C 'affect':488C 'allowlist':605C 'alongsid':609C 'alreadi':317C 'also':637C 'anyon':60B 'api':571C 'app/app.py':620C 'arbitrari':362C 'asgi':567C 'attack':331C,503C,528C 'auth':4A,49B,131C,157C,159C,167C,183C,202C,309C,543C,551C,574C,588C 'authent':12A,139C,176C,204C,639C 'auto':592C 'backend':56B,193C,196C 'backprop':2A,6A,96C 'backpropag':1A,44B,83C,92C,181C,200C,646C 'backpropagate':24A 'backpropagate/cli.py':243C 'backpropagate/ui_app':197C 'backpropagate/ui_security.py':458C 'base':367C 'basic':138C,573C 'bound':65B,224C,335C 'bug':305C 'bypass':450C,633C 'cannot':662C 'cap':420C,429C 'caus':409C 'chain':502C 'changelog':279C 'check':58B,310C 'claim':301C 'cli':120C,165C,635C 'cli.py':618C 'client':220C 'code':303C,463C 'code-on':302C 'combin':472C 'comment':241C,268C 'compar':307C 'confirm':171C 'content':347C 'contract':284C 'contradict':295C 'control':101C,130C,476C 'cooki':599C 'count':428C 'cred':594C 'credenti':59B 'cve':13A,21A 'cwe':26B,28B,30B 'data':493C,498C 'dataset':71B,103C,340C,439C 'dead':462C 'default':518C 'defens':613C 'depend':229C 'depth':615C 'dimens':325C 'direct':627C 'disclosur':319C 'disk':411C 'disk-fil':410C 'document':134C,147C,250C,277C,533C 'doesn':262C 'dos':413C 'download':378C 'enabl':168C 'endpoint':417C,489C 'enforc':11A,286C,638C 'entir':312C 'env':623C 'env-strip':622C 'everi':141C 'exfiltr':494C 'explicit':593C 'export':114C,180C,259C 'expos':98C,149C 'exposur':522C 'extens':422C 'f65r':18A 'face':78B,125C,272C,276C,299C 'fals':323C 'false-promis':322C 'featur':538C 'file':290C,351C 'fill':412C 'filter':423C 'fine':359C 'fine-tun':358C 'fix':556C 'flag':52B,126C,531C 'four':585C 'full':236C,653C 'g':650C 'gap':252C 'gate':281C,577C 'gguf':113C 'ghsa':17A 'ghsa-f65r-h4g3-3h9h':16A 'github.com':657C 'github.com/mcp-tool-shop-org/backpropagate/blob/main/changelog.md#120---2026-05-23':656C 'guard':212C,217C,636C 'h4g3':19A 'half':552C 'helper':454C 'hf':398C,477C,512C 'hmac':597C 'hmac-sign':596C 'host':41B,526C,603C 'host-loc':525C 'http':137C,578C 'hub':79B,117C,383C 'hug':77B 'huggingfac':32B,116C,380C,382C 'ignor':47B 'immedi':664C 'impact':329C 'implement':565C 'includ':346C 'inert':554C 'inlin':240C 'input':391C,484C 'instal':91C,372C,644C,649C 'intend':127C 'intern':271C 'internal-fac':270C 'invoc':632C 'jsonl/csv/txt':350C 'latest':652C 'launch':94C,190C 'layer':612C 'legitim':353C 'level':211C 'live':456C 'load':106C 'local':226C,373C,397C,516C,527C,589C 'local-on':515C 'm':629C 'make':486C 'mcptoolshop/backpropagate':25A,651C 'middlewar':205C,568C,607C 'miss':311C 'ml':34B 'ml-train':33B 'mode':586C 'model':105C,368C,434C,437C,441C,506C 'multi':110C 'multi-run':109C 'name':387C 'never':57B,198C 'note':655C 'npm':36B,647C,648C 'oper':124C,174C,298C,354C,370C,395C,408C,510C,539C 'operator-fac':123C,297C 'option':43B,86C 'orchestr':112C 'origin':604C 'own':405C 'packag':37B 'pass':133C,161C,163C,185C,545C 'patch':555C,564C 'path':315C,435C,438C,440C,443C,453C,459C,483C 'path-input':482C 'per':426C 'per-sess':425C 'phase':254C 'pip':90C,642C,643C 'plane':102C 'port':66B,225C,336C 'pre':601C 'preview':345C 'primarili':291C 'print':166C 'product':595C 'promis':324C 'public':154C,536C 'push':80B,118C,384C,478C,504C 'python':38B,628C 'rais':326C 'reach':63B,222C,333C 'read':68B,199C,264C,338C,433C,495C 'readm':278C 'real':566C 'reduc':521C 'reflex':55B,87C,192C,195C,261C,466C,630C 'regist':207C 'releas':559C,654C 'remot':228C 'render':341C 'repo':404C 'repositori':386C 'request':142C,210C 'request-level':209C 'requir':136C,156C 'rout':579C 'run':74B,111C,213C,218C,364C,631C 'runtim':294C 'rx.app':570C 'rx.upload':416C 'rxconfig.py':621C 'safe':452C 'scope':401C 'secur':129C,300C 'self':40B 'self-host':39B 'session':427C 'sever':328C 'shape':308C 'share':8A,51B,146C,232C,520C,530C,542C 'ship':280C,608C 'sign':598C 'size':419C 'sourc':249C,436C 'spoof':480C 'start':72B 'start/stop':108C 'strip':624C 'subject':392C 'subprocess':188C 'suitabl':490C 'summari':81C 'suppli':448C,501C 'supply-chain':500C 'surfac':467C,625C 'tamper':505C 'target':479C 'token':399C,591C 'train':35B,70B,73B,100C,107C,363C,475C,497C 'transform':572C,575C 'travers':485C 'trigger':76B,361C,381C 'tune':360C 'two':122C 'typic':402C 'ui':3A,7A,46B,89C,93C,97C,145C,151C,182C,201C,237C,344C,390C,619C 'unauthent':474C 'upgrad':216C,584C,640C,645C,663C 'upload':69B,104C,339C,356C,442C,496C 'url':537C 'use':234C,541C 'user':132C,160C,169C,184C,275C,447C,544C,661C 'user-fac':274C 'user-suppli':446C 'v1.1.0':248C,431C,469C 'v1.1.1':432C,470C 'v1.2.0':558C 'valid':460C,600C 'variabl':257C 'via':95C,388C,414C,569C 'warn':548C 'warrant':318C 'web':45B,88C 'websocket':215C,583C 'websocket.accept':602C 'weight':507C 'whether':231C 'workaround':659C 'would':316C 'yet':266C","nicknames":[],"mitigations":[],"who_affected":"Teams running Backpropagate 1.1.0 up to but not including 1.2.0 with the optional UI installed (`backpropagate[ui]`) are exposed. Exposure is highest when `--share` was used, since the UI then listens on a public address. Hosts that only ever ran the CLI for training, or that run 1.2.0 or later, are not affected.","tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-06-17T13:20:43+00:00","kev":null,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-f65r-h4g3-3h9h","url":"https://github.com/advisories/GHSA-f65r-h4g3-3h9h","severity":"critical","published_at":"2026-06-26T20:34:29+00:00","modified_at":"2026-10-27T16:16:12+00:00","first_seen_at":"2026-10-27T16:20:27+00:00","fetched_at":"2026-10-27T16:20:27+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-48797","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48797","severity":null,"published_at":"2026-06-17T13:20:43+00:00","modified_at":"2026-06-18T16:16:55+00:00","first_seen_at":"2026-10-27T16:33:07+00:00","fetched_at":"2026-10-27T16:33:07+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}