stemtrace 0.3.4__tar.gz → 0.3.5__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.
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.bumpversion.toml +1 -1
- {stemtrace-0.3.4 → stemtrace-0.3.5}/CHANGELOG.md +5 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/PKG-INFO +3 -3
- {stemtrace-0.3.4 → stemtrace-0.3.5}/README.md +1 -1
- {stemtrace-0.3.4 → stemtrace-0.3.5}/pyproject.toml +1 -1
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/__init__.py +1 -1
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/package.json +1 -1
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/static.py +53 -3
- stemtrace-0.3.5/tests/integration/test_static_ui.py +925 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/uv.lock +11 -11
- stemtrace-0.3.4/tests/integration/test_static_ui.py +0 -451
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.dockerignore +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.github/dependabot.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.github/workflows/ci.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.github/workflows/e2e.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.github/workflows/release.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.gitignore +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.pre-commit-config.yaml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/.python-version +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/CONTRIBUTING.md +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/Dockerfile +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/Dockerfile.e2e +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/LICENSE +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/Makefile +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/build_ui.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docker-compose.e2e.rabbitmq.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docker-compose.e2e.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docker-compose.rabbitmq.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docker-compose.yml +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docs/screenshots/task_details.png +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docs/screenshots/unregistered.png +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/docs/screenshots/workflow.png +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/examples/celery_app.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/examples/fastapi_integration.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/examples/with_auth.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/examples/with_login.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/progress.md +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/scripts/wait_for_http.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/core/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/core/events.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/core/exceptions.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/core/graph.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/core/ports.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/bootsteps.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/config.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/scrubbing.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/signals.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/transports/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/transports/memory.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/transports/rabbitmq.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/library/transports/redis.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/py.typed +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/__main__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/api/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/api/routes.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/api/schemas.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/api/websocket.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/consumer.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/fastapi/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/fastapi/auth.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/fastapi/extension.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/fastapi/form_auth.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/fastapi/login_routes.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/fastapi/router.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/store.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/biome.json +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/index.html +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/package-lock.json +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/api/client.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/ui/templates/login.html +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/src/stemtrace/server/websocket.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/conftest.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/e2e/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/e2e/conftest.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/e2e/tasks.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/e2e/test_api_e2e.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/e2e/test_workers_e2e.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/integration/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/integration/test_embedded_static.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/integration/test_fastapi_integration.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/integration/test_rabbitmq_transport.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/integration/test_redis_transport.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/integration/test_workers_refresh.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/__init__.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_auth.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_bootsteps.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_config.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_consumer.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_events.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_examples.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_exceptions.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_form_auth.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_form_login.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_graph.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_init.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_routes.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_scrubbing.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_signals.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_store.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_transports.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_ui_static_prefix.py +0 -0
- {stemtrace-0.3.4 → stemtrace-0.3.5}/tests/unit/test_websocket.py +0 -0
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.5] - 2026-10-09
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
- UI: fixed a path traversal in the static file handler. Crafted URLs could read any file the server process can access, not just the bundled frontend. When the UI is exposed without authentication (no form login or `auth_dependency`, which is the default for `stemtrace server` and the Docker image), no credentials were needed. All users should upgrade. If you can't upgrade right away, put the UI behind authentication or a reverse proxy that blocks it from untrusted networks.
|
|
14
|
+
|
|
10
15
|
## [0.3.4] - 2026-06-25
|
|
11
16
|
|
|
12
17
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: stemtrace
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: A lightweight Celery task flow visualizer
|
|
5
5
|
Project-URL: Homepage, https://github.com/iansokolskyi/stemtrace
|
|
6
6
|
Project-URL: Documentation, https://github.com/iansokolskyi/stemtrace#readme
|
|
@@ -56,7 +56,7 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
|
|
57
57
|
**Zero-infrastructure Celery task flow visualizer**
|
|
58
58
|
|
|
59
|
-
[](https://pypi.org/project/stemtrace)
|
|
60
60
|
[](https://pypi.org/project/stemtrace/)
|
|
61
61
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
62
62
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Zero-infrastructure Celery task flow visualizer**
|
|
4
4
|
|
|
5
|
-
[](https://pypi.org/project/stemtrace)
|
|
6
6
|
[](https://pypi.org/project/stemtrace/)
|
|
7
7
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
8
8
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -4,7 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
+
import os
|
|
7
8
|
import re
|
|
9
|
+
import stat
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
|
|
10
12
|
from fastapi import APIRouter, Request
|
|
@@ -160,6 +162,46 @@ def _rewrite_html_for_prefix(
|
|
|
160
162
|
return html.replace("<head>", script)
|
|
161
163
|
|
|
162
164
|
|
|
165
|
+
def _contained_frontend_path(path: str) -> str | None:
|
|
166
|
+
"""Confine a request path to the frontend directory, lexically.
|
|
167
|
+
|
|
168
|
+
The request path is user-controlled. It is joined onto the resolved
|
|
169
|
+
frontend directory and normalized as a string (dot-segments collapsed,
|
|
170
|
+
no symlink resolution), then accepted only if the result is still under
|
|
171
|
+
that directory. The filesystem is never touched for the candidate before
|
|
172
|
+
this check passes. Symlinks inside the frontend directory are trusted and
|
|
173
|
+
followed when the file is served (they come from the installed package,
|
|
174
|
+
e.g. symlink-mode installs), since only the request path is untrusted.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
path: Path parameter taken from the request URL.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
The normalized absolute path if it lies under the frontend directory
|
|
181
|
+
(it may not exist), or None if it escapes the directory.
|
|
182
|
+
"""
|
|
183
|
+
base = os.path.realpath(_FRONTEND_DIR)
|
|
184
|
+
candidate = os.path.normpath(Path(base) / path)
|
|
185
|
+
# Plain string prefix check: an absolute request path, a drive change or
|
|
186
|
+
# a UNC path (Windows) all produce a candidate outside this prefix.
|
|
187
|
+
base_prefix = base if base.endswith(os.sep) else base + os.sep
|
|
188
|
+
if candidate != base and not candidate.startswith(base_prefix):
|
|
189
|
+
return None
|
|
190
|
+
return candidate
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _is_regular_file(path: str) -> bool:
|
|
194
|
+
"""Return True if path is an existing regular file (symlinks followed).
|
|
195
|
+
|
|
196
|
+
Any error while inspecting the path (missing file, name too long,
|
|
197
|
+
embedded NUL byte, permission denied) is treated as "not a file".
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
return stat.S_ISREG(Path(path).stat().st_mode)
|
|
201
|
+
except (OSError, ValueError):
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
|
|
163
205
|
def get_static_router(*, show_logout: bool = False) -> APIRouter | None:
|
|
164
206
|
"""Create router for UI static files. Returns None if dist/ missing."""
|
|
165
207
|
return get_static_router_with_base(None, show_logout=show_logout)
|
|
@@ -220,9 +262,17 @@ def get_static_router_with_base(
|
|
|
220
262
|
|
|
221
263
|
@router.get("/{path:path}", response_model=None)
|
|
222
264
|
async def serve_spa(path: str, request: Request) -> FileResponse | HTMLResponse:
|
|
223
|
-
"""Serve static files or fall back to index.html for SPA routing.
|
|
224
|
-
|
|
225
|
-
|
|
265
|
+
"""Serve static files or fall back to index.html for SPA routing.
|
|
266
|
+
|
|
267
|
+
Only regular files under the frontend directory are served. Paths
|
|
268
|
+
that normalize to a location outside of it get a 404; any other path
|
|
269
|
+
that is not a servable file (missing, a directory, or not inspectable)
|
|
270
|
+
falls back to index.html so client-side routes keep working.
|
|
271
|
+
"""
|
|
272
|
+
file_path = _contained_frontend_path(path)
|
|
273
|
+
if file_path is None:
|
|
274
|
+
return HTMLResponse("<h1>Not found</h1>", status_code=404)
|
|
275
|
+
if _is_regular_file(file_path):
|
|
226
276
|
return FileResponse(file_path)
|
|
227
277
|
|
|
228
278
|
index_path = _FRONTEND_DIR / "index.html"
|