fix: best-effort avoid logging non-deadline directories in telemetry stack traces#1295
Conversation
… dirs The stack-trace sanitizer classified any path segment named after one of our framework packages (deadline, openjd, boto3, botocore) as safe-to-keep and emitted everything from that segment onward. A customer directory that merely shares a name with one of our packages (e.g. a project tree rooted at ~/deadline/secret_project/...) was therefore misclassified as framework, and its customer-specific path segments leaked into telemetry, violating the no-customer-content tenet. Tighten the match: a segment is only treated as framework when the reconstructed absolute prefix actually equals where that package is installed on this machine (resolved via importlib.util.find_spec). Genuine framework frames are still kept package-relative; the existing site-packages branch continues to cover ordinary venv installs. A customer "deadline" directory now redacts down to the bare filename. Adds example-based tests covering both the framework-kept and customer-redacted cases (Linux and Windows path shapes) alongside the existing property-based fuzz tests. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
| # site-packages are also covered by the generic site-packages branch | ||
| # below; this branch additionally handles embedded / custom-sys.path | ||
| # layouts where the package isn't under a site-packages directory.) | ||
| install_dirs = _known_package_install_dirs() |
There was a problem hiding this comment.
_known_package_install_dirs() is called once per _sanitize_path(), i.e. once per stack frame. Each call loops over every known package and runs importlib.util.find_spec(), which for not-yet-imported packages hits the import machinery/filesystem. The install locations are fixed for the life of the process, so a deep traceback pays this cost N times over for no benefit.
Consider memoizing — e.g. @functools.lru_cache(maxsize=None) on _known_package_install_dirs, or computing it once in _sanitize_traceback and threading it in. lru_cache also naturally handles the fact that the result never changes within a process.
There was a problem hiding this comment.
_known_package_install_dirs is memoized with @functools.lru_cache(maxsize=None), so the find_spec scan runs once per process rather than once per frame.
| locations = list(spec.submodule_search_locations or []) | ||
| pkg_dir = None | ||
| if locations: | ||
| pkg_dir = locations[0] |
There was a problem hiding this comment.
deadline and openjd are PEP 420 namespace packages (there is no src/deadline/__init__.py, and pyproject.toml sets namespace_packages = true). For a namespace package, submodule_search_locations can contain more than one path — e.g. when the namespace is contributed by several distributions installed into different sys.path roots (system site + user site, or an editable src tree alongside a wheel). This code records only locations[0] as the single anchor.
Consequence: a genuine framework frame that lives under one of the other namespace locations will not equal the anchor and will fall through — collapsing to a bare filename (or, if it happens to sit under a site-packages segment, salvaged by the later branch). This never leaks customer content (the failure is in the safe direction), but it does silently degrade the usefulness of the telemetry for legitimate framework frames.
Consider anchoring against all entries in submodule_search_locations, e.g. store Dict[str, List[str]] (or a set of normalized dirs) and match if the reconstructed prefix equals any of them.
There was a problem hiding this comment.
The anchor map is Dict[str, List[str]] and records every entry in submodule_search_locations, so a genuine framework frame under any namespace-package root is recognized; _sanitize_path matches the reconstructed prefix against all recorded anchors.
Memoize _known_package_install_dirs with lru_cache (install locations are fixed per process) and record all submodule_search_locations for PEP 420 namespace packages so genuine framework frames under any namespace root are recognized. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
…kages The telemetry test_sanitize_path cases assumed a bare package-name segment was enough to anchor the trim, but the sanitizer now only keeps a package-relative path when the on-disk prefix is the package's genuine install directory. Patch _known_package_install_dirs in the test so the fixture paths count as genuine install locations, making the cases machine-independent. Also treat Debian/Ubuntu dist-packages like site-packages in the generic fallback so system-Python installs keep the library-relative subpath. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
…s anchor Manual verification of the sanitizer found a remaining leak: a customer directory literally named "site-packages" (or "dist-packages") anywhere in a path — e.g. /home/artist/site-packages/ClientSecretShow/tool.py — anchored the generic fallback and leaked every segment below it into telemetry. Real interpreter layouts always place site-packages/dist-packages under a lib/Lib/lib64 or pythonX.Y parent (POSIX venv: lib/python3.11/site-packages, Windows venv: Lib\site-packages, Debian: lib/python3/dist-packages, Windows user site: Python311\site-packages), so require that corroborating parent before trusting the segment. Paths that fail the check fall through to the bare-filename branch — dropping detail is safe, leaking is not. Adds regression tests for the customer-named site-packages/dist-packages cases on POSIX and Windows path shapes, plus coverage for Debian dist-packages and Windows user site-packages layouts being kept relative. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
Parametrized cases for Windows layouts outside a venv: python.org all-users install under Program Files (path with a space), Microsoft Store Python under WindowsApps, and a customer-named deadline directory sitting above a genuine site-packages in the same path. Genuine package frames must render package-relative while no path segment above site-packages leaks. Replaces the manual spot-check previously flagged for a real Windows box — the sanitizer is pure path-string logic, so these shapes are fully exercisable in unit tests. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
Fixes:
What was the problem/requirement? (What/Why)
As a best-effort tidiness measure, the telemetry stack-trace sanitizer trims local file-path content so traces are shorter and less noisy, keeping only the portion from a known package (
deadline/openjd/boto3/botocore) onward. It did this by matching on segment name, so any path segment literally named after a package was treated as the package root. A directory that happens to be nameddeadline(e.g./home/user/deadline/my_project/file.py) was therefore trimmed at the wrong place, somy_projectand the filename stayed in the trace instead of being shortened away — noisier output than intended, and not the behavior we want.What was the solution? (How)
A segment is treated as a package root only when the reconstructed absolute prefix equals the package's genuine on-disk install location (resolved via
importlib.util.find_spec), rather than any path merely containing the package name. Thesite-packagesfallback still covers ordinary venv installs. A package that isn't importable in the running interpreter simply contributes no anchor, so its frames fall through to more trimming — the conservative direction (trim more, keep less).What is the impact of this change?
Genuine framework frames stay package-relative in telemetry; directories that happen to share a package name get trimmed to the bare filename like any other non-package path.
How was this change tested?
Added tests: genuine framework frames (
site-packages/deadline, third-party, Windows path) stay package-relative; adeadline-named directory that isn't the install location (Linux, Windows, rendered traceback, live exception) is trimmed to the bare filename.test_stack_trace_sanitizer.py(11 passed: 7 new + 4 existing).Was this change documented?
sanitize_exceptionsignature unchanged).Does this PR introduce new dependencies?
importlib.utilis stdlib.)Is this a breaking change?
No.
Does this change impact security?
No. This is a best-effort effort to keep telemetry stack traces tidy by trimming non-
deadlinedirectory names; it is not a security control and should not be treated as one. It is not relied on as a boundary, makes no guarantee about what path content a trace may contain, and any shortcoming here is an ordinary bug, not a security issue.Testing
Beyond the unit tests, the sanitizer was manually checked without live AWS by running fabricated stack traces through
_sanitize_path/_sanitize_traceback/sanitize_exceptionin a scratch harness, on both POSIX-style and Windows-style path sets:deadlinedirectory (/home/artist/deadline/some-project/job_bundle/render_script.py,C:\Users\bob\deadline\SomeProject\render.py), plain scripts, UNC shares (\\studio-nas\projects\...),deadline.egg-info-spelled dirs, and directories sharing a known package name (/opt/openjd/vendored/...).deadlineframe (hatch env src layout), realbotocoresite-packages frame, Debiandist-packagesframes, WindowsLib\site-packagesframes, and Windows user site-packages (...\Python311\site-packages\...).submodule_search_locationsroots for thedeadlinenamespace all anchor correctly while adeadline-named non-install dir does not.find_specruns at most once per known package per process across repeated sanitize calls (lru_cache).TracebackExceptionmixing all of the above rendered with the non-package path segments trimmed and no exception-message text.The manual pass found one case the earlier logic didn't trim as intended, fixed in this PR: a directory literally named
site-packagesordist-packages(e.g./home/artist/site-packages/SomeProject/tool.py) anchored the generic fallback and kept everything below it. The fallback now requires the corroborating interpreter-layout parent (lib/Lib/lib64/pythonX.Y) before trusting the segment; regression tests added for POSIX and Windows shapes.C:\Program Files\Python311\Lib\site-packages\...(path with a space), Microsoft Store Python underWindowsApps, per-user site-packages (...\AppData\Roaming\Python\Python311\site-packages\...), and adeadline-named directory sitting above a genuinesite-packagesin the same path — genuine package frames render package-relative and directories abovesite-packagesare trimmed. Since the sanitizer is pure path-string logic, these shapes are fully exercised in unit tests; no hands-on Windows testing is needed.hatch run fmt,hatch run lint(ruff + mypy) clean; full unit suite passes (2879 passed, 21 skipped).