dash-uploader-ng 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. dash_uploader_ng-1.0.0/LICENSE.txt +21 -0
  2. dash_uploader_ng-1.0.0/MANIFEST.in +11 -0
  3. dash_uploader_ng-1.0.0/PKG-INFO +84 -0
  4. dash_uploader_ng-1.0.0/README.md +199 -0
  5. dash_uploader_ng-1.0.0/SECURITY.md +140 -0
  6. dash_uploader_ng-1.0.0/dash_uploader_ng/__init__.py +59 -0
  7. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/Button.py +67 -0
  8. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/ProgressBar.py +58 -0
  9. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/Upload_ReactComponent.py +181 -0
  10. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/__init__.py +0 -0
  11. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/_imports_.py +9 -0
  12. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/dash_uploader_ng.min.js +3 -0
  13. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/dash_uploader_ng.min.js.LICENSE.txt +3 -0
  14. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/dash_uploader_ng.min.js.map +1 -0
  15. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/metadata.json +1 -0
  16. dash_uploader_ng-1.0.0/dash_uploader_ng/_build/package-info.json +59 -0
  17. dash_uploader_ng-1.0.0/dash_uploader_ng/callbacks.py +135 -0
  18. dash_uploader_ng-1.0.0/dash_uploader_ng/configure_upload.py +110 -0
  19. dash_uploader_ng-1.0.0/dash_uploader_ng/httprequesthandler.py +334 -0
  20. dash_uploader_ng-1.0.0/dash_uploader_ng/safepath.py +194 -0
  21. dash_uploader_ng-1.0.0/dash_uploader_ng/settings.py +20 -0
  22. dash_uploader_ng-1.0.0/dash_uploader_ng/upload.py +178 -0
  23. dash_uploader_ng-1.0.0/dash_uploader_ng/uploadstatus.py +82 -0
  24. dash_uploader_ng-1.0.0/dash_uploader_ng/utils.py +67 -0
  25. dash_uploader_ng-1.0.0/dash_uploader_ng.egg-info/PKG-INFO +84 -0
  26. dash_uploader_ng-1.0.0/dash_uploader_ng.egg-info/SOURCES.txt +36 -0
  27. dash_uploader_ng-1.0.0/dash_uploader_ng.egg-info/dependency_links.txt +1 -0
  28. dash_uploader_ng-1.0.0/dash_uploader_ng.egg-info/requires.txt +6 -0
  29. dash_uploader_ng-1.0.0/dash_uploader_ng.egg-info/top_level.txt +1 -0
  30. dash_uploader_ng-1.0.0/docs/CHANGELOG.md +118 -0
  31. dash_uploader_ng-1.0.0/docs/README-PyPi.md +52 -0
  32. dash_uploader_ng-1.0.0/package.json +59 -0
  33. dash_uploader_ng-1.0.0/pyproject.toml +96 -0
  34. dash_uploader_ng-1.0.0/setup.cfg +4 -0
  35. dash_uploader_ng-1.0.0/tests/test_safepath.py +174 -0
  36. dash_uploader_ng-1.0.0/tests/test_security_cve_2026_38360.py +305 -0
  37. dash_uploader_ng-1.0.0/tests/test_uploadstatus.py +22 -0
  38. dash_uploader_ng-1.0.0/tests/test_upstream_parity.py +363 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020- Niko Föhr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ include dash_uploader_ng/_build/dash_uploader_ng.min.js
2
+ include dash_uploader_ng/_build/dash_uploader_ng.min.js.map
3
+ include dash_uploader_ng/_build/dash_uploader_ng.min.js.LICENSE.txt
4
+ include dash_uploader_ng/_build/metadata.json
5
+ include dash_uploader_ng/_build/package-info.json
6
+ include README.md
7
+ include SECURITY.md
8
+ include docs/README-PyPi.md
9
+ include docs/CHANGELOG.md
10
+ include LICENSE.txt
11
+ include package.json
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: dash-uploader-ng
3
+ Version: 1.0.0
4
+ Summary: Large file upload component for Dash -- a maintained, security-patched fork of dash-uploader
5
+ Author-email: Niko Föhr <fohrloop@gmail.com>
6
+ Maintainer: Awwab Mahdi, Claude (Anthropic)
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/obliviance/dash-uploader-ng
9
+ Project-URL: Repository, https://github.com/obliviance/dash-uploader-ng
10
+ Project-URL: Issues, https://github.com/obliviance/dash-uploader-ng/issues
11
+ Project-URL: Security, https://github.com/obliviance/dash-uploader-ng/blob/main/SECURITY.md
12
+ Keywords: dash,upload,plotly,flow.js,file-upload
13
+ Classifier: Framework :: Dash
14
+ Classifier: Framework :: Flask
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.txt
26
+ Requires-Dist: dash>=2.0
27
+ Requires-Dist: packaging>=21.0
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=7.0; extra == "test"
30
+ Requires-Dist: flask>=2.0; extra == "test"
31
+ Dynamic: license-file
32
+
33
+
34
+ ![PyPI](https://img.shields.io/pypi/v/dash-uploader-ng)&nbsp;![PyPI - Downloads](https://img.shields.io/pypi/dm/dash-uploader-ng)&nbsp;![Python versions](https://img.shields.io/pypi/pyversions/dash-uploader-ng)&nbsp;![License](https://img.shields.io/pypi/l/dash-uploader-ng)
35
+
36
+ ![upload large files with dash-uploader-ng](https://raw.githubusercontent.com/obliviance/dash-uploader-ng/main/docs/upload-demo.gif)
37
+
38
+ # 📤 dash-uploader-ng
39
+ The upload package for [Dash](https://dash.plotly.com/) applications using large data files. A maintained, security-patched fork of [dash-uploader](https://github.com/fohrloop/dash-uploader).
40
+
41
+ ### 🏠 Homepage & Documentation
42
+ [https://github.com/obliviance/dash-uploader-ng](https://github.com/obliviance/dash-uploader-ng)
43
+
44
+
45
+ ## Short summary
46
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 💾 Data file size has no limits. (Except the hard disk size)<bR>
47
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ☎ Call easily a callback after uploading is finished.<br>
48
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 📦 Upload files using [flow.js](https://github.com/flowjs/flow.js/)
49
+ <br>
50
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ✅ Works with Dash 2.0+ (verified on Dash 4.x) & Python 3.10+.<br>
51
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 🔒 Security-patched fork of dash-uploader (fixes CVE-2026-38360).<br>
52
+
53
+
54
+
55
+ ## Installing
56
+ ```
57
+ pip install dash-uploader-ng
58
+ ```
59
+
60
+ ## Usage
61
+
62
+
63
+ ### Simple example
64
+
65
+ ```python
66
+ import dash
67
+ from dash import html
68
+ import dash_uploader_ng as du
69
+
70
+ app = dash.Dash(__name__)
71
+
72
+ # 1) configure the upload folder
73
+ du.configure_upload(app, r"C:\tmp\Uploads")
74
+
75
+ # 2) Use the Upload component
76
+ app.layout = html.Div([
77
+ du.Upload(),
78
+ ])
79
+
80
+ if __name__ == '__main__':
81
+ app.run(debug=True)
82
+
83
+ ```
84
+
@@ -0,0 +1,199 @@
1
+ # 📤 dash-uploader-ng
2
+
3
+ A maintained, **security-patched** fork of
4
+ [dash-uploader](https://github.com/fohrloop/dash-uploader) — the upload
5
+ component for [Dash](https://dash.plotly.com/) applications that need large
6
+ data files.
7
+
8
+ Upstream dash-uploader was archived on 2025-07-19 without a fix for
9
+ [CVE-2026-38360](SECURITY.md), a critical (CVSS 9.8) path-traversal → RCE in
10
+ the upload endpoint. This fork **fixes that vulnerability**, modernizes the
11
+ packaging, and refreshes the dependency tree while keeping the same API.
12
+
13
+ > **Migrating from `dash-uploader`:** the import name changed, so update your
14
+ > imports. Nothing else in your app needs to change.
15
+ > ```diff
16
+ > - import dash_uploader as du
17
+ > + import dash_uploader_ng as du
18
+ > ```
19
+
20
+ - 🔒 **CVE-2026-38360 fixed** — see [SECURITY.md](SECURITY.md).
21
+ - 🧰 **Modernized** — `pyproject.toml`, Python 3.10–3.13, `dash>=2.0` (verified
22
+ on dash 4.x), zero known npm vulnerabilities in the build.
23
+ - 🤝 **Drop-in API** — same `configure_upload` / `Upload` / `callback` /
24
+ `UploadStatus` as upstream.
25
+
26
+
27
+
28
+ ## Table of contents
29
+ - [Short summary](#short-summary)
30
+ - [dash-uploader vs. dcc.Upload](#dash-uploader-vs-dccupload)
31
+ - [Installing](#installing)
32
+ - [Quickstart](#quickstart)
33
+ - [Simple example](#simple-example)
34
+ - [Example with callback](#example-with-callback-and-other-options)
35
+ - [Contributing](#contributing)
36
+ - [Documentation](#documentation)
37
+ - [Changelog](#changelog)
38
+ - [Credits](#credits)
39
+
40
+ ## Short summary
41
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 💾 Data file size has no limits. (Except the hard disk size)<bR>
42
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ☎ Call easily a callback after uploading is finished.<br>
43
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 📤 Upload files using [flow.js](https://github.com/flowjs/flow.js/) <br>
44
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 📦 All JS and CSS bundled with the package. No need for network calls for CSS/JS.<br>
45
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ✅ Works with Dash 2.0+ (verified on Dash 4.x) & Python 3.10+.<br>
46
+
47
+
48
+ ### dash-uploader vs. [dcc.Upload](https://dash.plotly.com/dash-core-components/upload)
49
+
50
+
51
+
52
+
53
+
54
+ | | dash-uploader | [dcc.Upload](https://dash.plotly.com/dash-core-components/upload) |
55
+ | --------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
56
+ | Underlying technology | [flow.js](https://github.com/flowjs/flow.js/) | HTML5 API |
57
+ | File size | Unlimited | max ~150-200MB ([link](https://community.plotly.com/t/dash-upload-component-decoding-large-files/8033/11)) |
58
+ | Uploads to | Hard disk (server side) | First to browser memory (user side) Then, to server using callbacks. |
59
+ | Data type | Uploaded as file; no need to parse at server side. | Uploaded as byte64 encoded string -> Needs parsing |
60
+ | See upload progress? | Progressbar out of the box | No upload indicators out of the box. Generic loading indicator possible. [Progressbar not possible](https://community.plotly.com/t/upload-after-confirmation-and-progress-bar/7172). |
61
+
62
+ # Installing
63
+ ```
64
+ pip install dash-uploader-ng
65
+ ```
66
+
67
+ # Quickstart
68
+
69
+ Full documentation [here](docs/dash-uploader.md)
70
+ >⚠️**Security note**: The Upload component allows POST requests and uploads of arbitrary files to the server harddisk and one should take this into account (with user token checking etc.) if used as part of a public website! For this you can utilize the `http_request_handler` argument of the [du.configure_upload](https://github.com/fohrloop/dash-uploader/blob/master/docs/dash-uploader.md#duconfigure_upload). (New in version 0.5.0)
71
+
72
+ ## Simple example
73
+
74
+ ```python
75
+ import dash
76
+ from dash import html
77
+ import dash_uploader_ng as du
78
+
79
+ app = dash.Dash(__name__)
80
+
81
+ # 1) configure the upload folder
82
+ du.configure_upload(app, r"C:\tmp\Uploads")
83
+
84
+ # 2) Use the Upload component
85
+ app.layout = html.Div([
86
+ du.Upload(),
87
+ ])
88
+
89
+ if __name__ == '__main__':
90
+ app.run(debug=True)
91
+
92
+ ```
93
+
94
+ ## Example with callback (and other options)
95
+ - **New in version 0.7.0:** Functions decorated with [`@du.callback`](https://github.com/fohrloop/dash-uploader/blob/dev/docs/dash-uploader.md#ducallback) must take `status` ([`du.UploadStatus`](https://github.com/fohrloop/dash-uploader/blob/dev/docs/dash-uploader.md#duuploadstatus)) object as the only input parameter (previously, `filenames`)
96
+
97
+
98
+ ```python
99
+ from pathlib import Path
100
+ import uuid
101
+
102
+ import dash_uploader_ng as du
103
+ import dash
104
+ from dash import html
105
+ from dash.dependencies import Input, Output, State
106
+
107
+ app = dash.Dash(__name__)
108
+
109
+ UPLOAD_FOLDER_ROOT = r"C:\tmp\Uploads"
110
+ du.configure_upload(app, UPLOAD_FOLDER_ROOT)
111
+
112
+ def get_upload_component(id):
113
+ return du.Upload(
114
+ id=id,
115
+ max_file_size=1800, # 1800 MB
116
+ filetypes=['csv', 'zip'],
117
+ upload_id=uuid.uuid1(), # Unique session id
118
+ )
119
+
120
+
121
+ def get_app_layout():
122
+
123
+ return html.Div(
124
+ [
125
+ html.H1('Demo'),
126
+ html.Div(
127
+ [
128
+ get_upload_component(id='dash-uploader'),
129
+ html.Div(id='callback-output'),
130
+ ],
131
+ style={ # wrapper div style
132
+ 'textAlign': 'center',
133
+ 'width': '600px',
134
+ 'padding': '10px',
135
+ 'display': 'inline-block'
136
+ }),
137
+ ],
138
+ style={
139
+ 'textAlign': 'center',
140
+ },
141
+ )
142
+
143
+
144
+ # get_app_layout is a function
145
+ # This way we can use unique session id's as upload_id's
146
+ app.layout = get_app_layout
147
+
148
+
149
+ @du.callback(
150
+ output=Output("callback-output", "children"),
151
+ id="dash-uploader",
152
+ )
153
+ def callback_on_completion(status: du.UploadStatus):
154
+ return html.Ul([html.Li(str(x)) for x in status.uploaded_files])
155
+
156
+
157
+
158
+ if __name__ == '__main__':
159
+ app.run(debug=True)
160
+
161
+ ```
162
+
163
+
164
+ ## Contributing
165
+
166
+
167
+ | What? | How? |
168
+ | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | 🐞 Found a bug? | 🎟 <a href="https://github.com/fohrloop/dash-uploader/issues">File an Issue</a> |
170
+ | 🙋‍♂️ Need help? | ❔ <a href="https://stackoverflow.com/questions/ask">Ask a question on StackOverflow</a> <br><a href="https://community.plotly.com/t/show-and-tell-dash-uploader-upload-large-files/38451">📧 Use this thread on community.plotly.com</a> |
171
+ | 💡 Want to submit a feature request? | <a href="https://community.plotly.com/t/show-and-tell-dash-uploader-upload-large-files/38451">🎭 Discuss about it on community.plotly.com</a><br><a href="https://github.com/fohrloop/dash-uploader/issues">🎫 File an Issue (feature request)</a> |
172
+ | 🧙 Want to write code? | 🔥 <a href="./docs/CONTRIBUTING.md">Here's how you get started!</a> |
173
+ ## Documentation
174
+ - [Documentation](docs/dash-uploader.md)
175
+ - [Developer documentation](docs/CONTRIBUTING.md)
176
+ - [Migration Guide](https://github.com/fohrloop/dash-uploader/wiki/Migration-Guide)
177
+
178
+ ## Changelog
179
+
180
+ - [Changelog](./docs/CHANGELOG.md)
181
+
182
+ ## Maintainers
183
+
184
+ - **Awwab Mahdi** — human maintainer. Security reports and issues go to
185
+ [GitHub](https://github.com/obliviance/dash-uploader-ng/security/advisories/new);
186
+ see [SECURITY.md](SECURITY.md).
187
+ - **Claude (Anthropic)** — the CVE-2026-38360 fix, the modernization, and the
188
+ upstream parity test suite in this fork.
189
+
190
+ `dash-uploader-ng` is an independent fork and is **not** affiliated with or
191
+ endorsed by the original author. Original authorship is retained in
192
+ [LICENSE.txt](LICENSE.txt) and in the package's `authors` metadata.
193
+
194
+ ## Credits
195
+ - History: This package is based on the React 16 compatible version [dash-resumable-upload](https://github.com/westonkjones/dash-resumable-upload) (0.0.4) by [Weston Jones](https://github.com/westonkjones/) which in turn is based on [dash-resumable-upload](https://github.com/rmarren1/dash-resumable-upload) (0.0.3) by [Ryan Marren](https://github.com/rmarren1)
196
+ - The package boilerplate is taken from the [dash-component-boilerplate](https://github.com/plotly/dash-component-boilerplate).
197
+ - The uploading JS function utilizes the [flow.js](https://github.com/flowjs/flow.js/) (2.14.x).
198
+ - The JS component is created using [React](https://github.com/facebook/react) (17.0.x)
199
+ - The CSS styling is mostly from [Bootstrap](https://getbootstrap.com/) 4.
@@ -0,0 +1,140 @@
1
+ # Security
2
+
3
+ ## CVE-2026-38360 — path traversal → RCE (fixed in this fork)
4
+
5
+ **Affected:** upstream `dash-uploader`, all versions `0.1.0` through `0.7.0a2`.
6
+ **Severity:** Critical — CWE-22, CVSS 3.1 score 9.8 (network / no auth / low complexity).
7
+ **Status upstream:** unpatched; the repository was archived on 2025-07-19.
8
+ **Status here:** fixed in `dash-uploader-ng` from `1.0.0` onward.
9
+
10
+ ### What it was
11
+
12
+ The upload endpoint (`/API/dash-uploader` by default) took three form fields
13
+ straight off an unauthenticated `POST` and joined them onto the upload
14
+ directory without checking that the result stayed inside it:
15
+
16
+ - `upload_id` — used as a directory name under the upload root
17
+ - `flowFilename` — used as the saved file's name
18
+ - `flowIdentifier` — used as a per-upload temp directory name
19
+
20
+ A single request was enough to write outside the upload root:
21
+
22
+ ```
23
+ upload_id=../../../../usr/local/lib/python3.10/site-packages
24
+ flowFilename=evil.pth
25
+ ```
26
+
27
+ Writing a `.pth` file into `site-packages` turns an arbitrary file write into
28
+ **remote code execution**: Python executes `.pth` files on the next interpreter
29
+ start. Other primitives (dropping `sitecustomize.py`, overwriting a module or a
30
+ WSGI entry point, planting a script in a cron directory) get there too.
31
+
32
+ ### How it's fixed
33
+
34
+ Validation happens once, at the request boundary, in
35
+ [`dash_uploader_ng/safepath.py`](dash_uploader_ng/safepath.py), in two layers:
36
+
37
+ 1. **Reject bad components up front.** `upload_id` and `flowIdentifier` must be
38
+ a single safe path segment; `flowFilename` / `flowRelativePath` have any
39
+ directory part stripped (folder uploads are flattened, as upstream
40
+ documented) and the remainder validated. A rejected request returns
41
+ **HTTP 400**, not a 500.
42
+ 2. **Re-check the resolved path before every write.** `ensure_within()`
43
+ resolves the candidate and the upload root and refuses anything that lands
44
+ outside the root — this also catches escapes that only appear after symlink
45
+ resolution, which a purely lexical check on the form fields cannot see.
46
+
47
+ Filenames are **not** run through `werkzeug.utils.secure_filename`, on purpose:
48
+ it ASCII-folds, so it would silently turn `données.csv` into `donnes.csv` and
49
+ empty out a fully non-Latin filename. The fork strips separators and validates
50
+ the remainder instead, so real-world filenames survive intact.
51
+
52
+ The handler was also hardened against a missing/absurd `flowTotalChunks` and an
53
+ out-of-range `flowChunkNumber`, which previously crashed with a 500.
54
+
55
+ ### Tests
56
+
57
+ - [`tests/test_safepath.py`](tests/test_safepath.py) — the validation helpers in
58
+ isolation (accept/reject tables, including a symlink-escape case).
59
+ - [`tests/test_security_cve_2026_38360.py`](tests/test_security_cve_2026_38360.py)
60
+ — the published proof of concept and variants fired at a live Flask app,
61
+ asserting against the filesystem that nothing lands outside the upload root,
62
+ plus regression tests that ordinary and non-ASCII uploads still work.
63
+
64
+ - [`tests/test_upstream_parity.py`](tests/test_upstream_parity.py) —
65
+ **differential tests** that run this fork's handler and the *pristine
66
+ upstream handler* (vendored at `tests/_upstream/`) over the same requests and
67
+ diff the results. Legitimate traffic must behave identically; hostile traffic
68
+ must not.
69
+
70
+ The first three run headless: `pytest` (no browser required). Upstream's
71
+ Selenium suite lives in `tests/browser/` and also passes — see
72
+ [Compatibility](#compatibility-with-dash-uploader) below.
73
+
74
+ ## What is actually exploitable, per vector
75
+
76
+ Measured by running the real upstream code, not inferred from the advisory:
77
+
78
+ | Vector | Upstream behaviour | This fork |
79
+ | --- | --- | --- |
80
+ | `upload_id` traversal | **200 OK, writes outside the upload root** — the RCE primitive | 400, nothing written |
81
+ | `flowIdentifier` traversal | Writes outside the root, but only **observably on an incomplete upload** — a completed one `rmtree()`s the evidence | 400, nothing written |
82
+ | `flowFilename` traversal | **500** — dies on `FileNotFoundError` before it can escape; a denial of service, not an arbitrary write | 200, flattened to a bare filename inside the root |
83
+
84
+ Two things worth knowing here. The `flowIdentifier` vector looks harmless if
85
+ you test it with a single-chunk upload, because the escaped directory is the
86
+ temp chunk directory and upstream deletes it on completion — declare more
87
+ chunks than you send and the escaped file simply stays. And the advisory is
88
+ *more* pessimistic than the code for `flowFilename`: that one crashes rather
89
+ than writing anywhere.
90
+
91
+ ## Compatibility with dash-uploader
92
+
93
+ The fork is a drop-in for everything a real flow.js client sends. Verified two
94
+ ways:
95
+
96
+ - **Upstream's own browser suite passes** — 7 passed, 1 skipped (upstream's own
97
+ Windows-only marker), against Chromium in headless mode, including a real
98
+ end-to-end file upload through the browser.
99
+ - **Differential tests** confirm byte-identical results to upstream for
100
+ single-chunk, multi-chunk, re-upload, empty, binary, and non-ASCII
101
+ (`données.csv`, `отчет.csv`, `報告書.xlsx`) uploads, with and without
102
+ `use_upload_id`.
103
+
104
+ ### Deliberate behaviour changes
105
+
106
+ These are the cases where the fork does **not** match upstream. All are pinned
107
+ by tests so they cannot drift silently.
108
+
109
+ 1. **Stricter `upload_id`.** Values upstream accepted are now rejected with a
110
+ 400: spaces (`my session`), colons (`session:42`), non-ASCII (`café`),
111
+ leading dots (`.hidden`), trailing dots (`session.`). uuid1/uuid4 — what
112
+ `du.Upload()` generates by default — are unaffected. If your app passes its
113
+ own `upload_id`, map it to `[A-Za-z0-9._-]` starting with an alphanumeric
114
+ first. The alternative was hand-reasoning about which "harmless" oddities
115
+ compose into a traversal on which filesystem.
116
+ 2. **Errors are real HTTP errors.** Upstream logged the exception and returned
117
+ `None`, which Flask rejected as an invalid response — so failures surfaced
118
+ as an accidental 500, and the `abort()` calls inside `_get` were swallowed
119
+ and became 500s too. Malformed requests are now 400, genuine faults 500, and
120
+ `_get`'s 404 is a 404.
121
+ 3. **`post_after` / `get_after` run on success only.** A failing request now
122
+ propagates as an HTTP error response instead of being swallowed, so those
123
+ hooks no longer fire on failure. Override `post`/`get` if you need a hook
124
+ that runs either way.
125
+ 4. **A path in `flowFilename` is flattened, not fatal.** Upstream 500s; the
126
+ fork strips the directory part and saves the bare filename.
127
+
128
+ ## Reporting a vulnerability
129
+
130
+ Report through **GitHub**, which is the channel a human actually monitors:
131
+
132
+ - Preferred, for anything sensitive: open a private security advisory at
133
+ <https://github.com/obliviance/dash-uploader-ng/security/advisories/new>
134
+ - Otherwise: <https://github.com/obliviance/dash-uploader-ng/issues>
135
+
136
+ Please don't route security reports to the package metadata. `Claude
137
+ (Anthropic)` is credited as a maintainer for the work on this fork, but it is
138
+ an AI assistant and cannot receive or triage reports; **Awwab Mahdi** is the
139
+ human maintainer. Nothing sensitive should be sent anywhere but the two links
140
+ above.
@@ -0,0 +1,59 @@
1
+ import os as _os
2
+ import sys as _sys
3
+ import json
4
+
5
+ import dash as _dash
6
+
7
+ from dash_uploader_ng.configure_upload import configure_upload
8
+ from dash_uploader_ng.callbacks import callback
9
+ from dash_uploader_ng.httprequesthandler import HttpRequestHandler
10
+ from dash_uploader_ng.upload import Upload
11
+ from dash_uploader_ng.uploadstatus import UploadStatus
12
+
13
+ # noinspection PyUnresolvedReferences
14
+ from ._build._imports_ import * # noqa: F403,F401
15
+ from ._build._imports_ import __all__ as build_all
16
+
17
+ # Defines all exposed APIs of this package.
18
+ __all__ = [
19
+ "configure_upload",
20
+ "callback",
21
+ "HttpRequestHandler",
22
+ "Upload",
23
+ "UploadStatus",
24
+ ]
25
+
26
+ if not hasattr(_dash, "development"):
27
+ print(
28
+ "Dash was not successfully imported. "
29
+ "Make sure you don't have a file "
30
+ 'named \n"dash.py" in your current directory.',
31
+ file=_sys.stderr,
32
+ )
33
+ _sys.exit(1)
34
+
35
+ _basepath = _os.path.dirname(__file__)
36
+ _filepath = _os.path.abspath(_os.path.join(_basepath, "_build", "package-info.json"))
37
+ with open(_filepath) as f:
38
+ package = json.load(f)
39
+
40
+ package_name = package["name"].replace(" ", "_").replace("-", "_")
41
+ __version__ = package["version"]
42
+
43
+ _current_path = _os.path.dirname(_os.path.abspath(__file__))
44
+
45
+ _this_module = _sys.modules[__name__]
46
+ _js_dist = [
47
+ {"relative_package_path": "_build/dash_uploader_ng.min.js", "namespace": package_name},
48
+ {
49
+ "relative_package_path": "_build/dash_uploader_ng.min.js.map",
50
+ "namespace": package_name,
51
+ "dynamic": True,
52
+ },
53
+ ]
54
+
55
+ _css_dist = []
56
+
57
+ for _component in build_all:
58
+ setattr(locals()[_component], "_js_dist", _js_dist)
59
+ setattr(locals()[_component], "_css_dist", _css_dist)
@@ -0,0 +1,67 @@
1
+ # AUTO GENERATED FILE - DO NOT EDIT
2
+
3
+ import typing # noqa: F401
4
+ from typing_extensions import TypedDict, NotRequired, Literal # noqa: F401
5
+ from dash.development.base_component import Component, _explicitize_args
6
+ try:
7
+ from dash.types import NumberType # noqa: F401
8
+ except ImportError:
9
+ # Backwards compatibility for dash<=4.1.0
10
+ if typing.TYPE_CHECKING:
11
+ raise
12
+ NumberType = typing.Union[ # noqa: F401
13
+ typing.SupportsFloat, typing.SupportsInt, typing.SupportsComplex
14
+ ]
15
+
16
+ ComponentSingleType = typing.Union[str, int, float, Component, None]
17
+ ComponentType = typing.Union[
18
+ ComponentSingleType,
19
+ typing.Sequence[ComponentSingleType],
20
+ ]
21
+
22
+
23
+ class Button(Component):
24
+ """A Button component.
25
+ A Button component.
26
+ Used as a part of Upload component.
27
+
28
+ Keyword arguments:
29
+
30
+ - btnClass (string; default 'dash-uploader-btn'):
31
+ The CSS class for the button.
32
+
33
+ - disabled (boolean; default False):
34
+ Is disabled, the component is not shown.
35
+
36
+ - isUploading (boolean; default False):
37
+ Is True, the parent component has upload in progress.
38
+
39
+ - text (string; default ''):
40
+ The text on the button."""
41
+ _children_props: typing.List[str] = []
42
+ _base_nodes = ['children']
43
+ _namespace = 'dash_uploader_ng'
44
+ _type = 'Button'
45
+
46
+
47
+ def __init__(
48
+ self,
49
+ text: typing.Optional[str] = None,
50
+ btnClass: typing.Optional[str] = None,
51
+ onClick: typing.Optional[typing.Any] = None,
52
+ disabled: typing.Optional[bool] = None,
53
+ isUploading: typing.Optional[bool] = None,
54
+ **kwargs
55
+ ):
56
+ self._prop_names = ['btnClass', 'disabled', 'isUploading', 'text']
57
+ self._valid_wildcard_attributes = []
58
+ self.available_properties = ['btnClass', 'disabled', 'isUploading', 'text']
59
+ self.available_wildcard_properties = []
60
+ _explicit_args = kwargs.pop('_explicit_args')
61
+ _locals = locals()
62
+ _locals.update(kwargs) # For wildcard attrs and excess named props
63
+ args = {k: _locals[k] for k in _explicit_args}
64
+
65
+ super(Button, self).__init__(**args)
66
+
67
+ setattr(Button, "__init__", _explicitize_args(Button.__init__))
@@ -0,0 +1,58 @@
1
+ # AUTO GENERATED FILE - DO NOT EDIT
2
+
3
+ import typing # noqa: F401
4
+ from typing_extensions import TypedDict, NotRequired, Literal # noqa: F401
5
+ from dash.development.base_component import Component, _explicitize_args
6
+ try:
7
+ from dash.types import NumberType # noqa: F401
8
+ except ImportError:
9
+ # Backwards compatibility for dash<=4.1.0
10
+ if typing.TYPE_CHECKING:
11
+ raise
12
+ NumberType = typing.Union[ # noqa: F401
13
+ typing.SupportsFloat, typing.SupportsInt, typing.SupportsComplex
14
+ ]
15
+
16
+ ComponentSingleType = typing.Union[str, int, float, Component, None]
17
+ ComponentType = typing.Union[
18
+ ComponentSingleType,
19
+ typing.Sequence[ComponentSingleType],
20
+ ]
21
+
22
+
23
+ class ProgressBar(Component):
24
+ """A ProgressBar component.
25
+ A ProgressBar component.
26
+ Used as a part of Upload component.
27
+
28
+ Keyword arguments:
29
+
30
+ - isUploading (boolean; default False):
31
+ The upload status (boolean).
32
+
33
+ - progressBar (number; default 0):
34
+ The progressbar value."""
35
+ _children_props: typing.List[str] = []
36
+ _base_nodes = ['children']
37
+ _namespace = 'dash_uploader_ng'
38
+ _type = 'ProgressBar'
39
+
40
+
41
+ def __init__(
42
+ self,
43
+ progressBar: typing.Optional[NumberType] = None,
44
+ isUploading: typing.Optional[bool] = None,
45
+ **kwargs
46
+ ):
47
+ self._prop_names = ['isUploading', 'progressBar']
48
+ self._valid_wildcard_attributes = []
49
+ self.available_properties = ['isUploading', 'progressBar']
50
+ self.available_wildcard_properties = []
51
+ _explicit_args = kwargs.pop('_explicit_args')
52
+ _locals = locals()
53
+ _locals.update(kwargs) # For wildcard attrs and excess named props
54
+ args = {k: _locals[k] for k in _explicit_args}
55
+
56
+ super(ProgressBar, self).__init__(**args)
57
+
58
+ setattr(ProgressBar, "__init__", _explicitize_args(ProgressBar.__init__))