Repository navigation
fix: fail the PHI scan closed and drop the pip runtime dependency - #21
Merged
Merged
Conversation
openadapt-privacy-scan printed "PHI scan clean (0 files)" and exited 0 when --root did not exist, pointed at a file, or pointed at an empty directory. It also skipped directories and files it could not read and still reported clean. A mistyped path in CI turned the gate into a silent pass. The scan now exits 2 when it cannot run: the root is missing or is not a directory, a directory under it cannot be listed (from os.walk or from git ls-files, which warns and exits 0), or it has no files to scan. --allow-empty opts in to an empty tree. A file that cannot be opened is reported as an unreadable finding (exit 1) instead of being skipped as binary. scan_tree() raises OSError for a missing or non-directory root. The clean message counts the files that were scanned, and the README lists the exit codes. Ledger: B123 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pyproject.toml listed "pip>=25.3" as a runtime dependency. The package never imports or runs pip, but installing openadapt-privacy (or any package that depends on it) into an environment with an older pip upgraded the user's pip, and it conflicted with environments that pin pip on purpose. Remove pip from the runtime dependencies. Keep the pip>=25.3 floor in the one CI step that installs pip for the spaCy model download. A new test checks that the runtime dependencies and user extras do not list pip, setuptools, wheel, or uv. Ledger: B124 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Treat a git permission warning as a failed listing only when it names a path in the tree. An unreadable global ignore file under $HOME no longer turns a complete scan into "Nothing was scanned" (exit 2). - Check each listed path with os.stat and raise on a permission error. On Python 3.14, Path.is_file() returns False instead, so files under a directory without search permission dropped out and the scan reported clean. - Exit 2, not a traceback, when the root's parent can't be searched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
abrichr
added a commit
that referenced
this pull request
Oct 18, 2026
* fix: fail the PHI scan when the root is missing, empty, or unreadable openadapt-privacy-scan printed "PHI scan clean (0 files)" and exited 0 when --root did not exist, pointed at a file, or pointed at an empty directory. It also skipped directories and files it could not read and still reported clean. A mistyped path in CI turned the gate into a silent pass. The scan now exits 2 when it cannot run: the root is missing or is not a directory, a directory under it cannot be listed (from os.walk or from git ls-files, which warns and exits 0), or it has no files to scan. --allow-empty opts in to an empty tree. A file that cannot be opened is reported as an unreadable finding (exit 1) instead of being skipped as binary. scan_tree() raises OSError for a missing or non-directory root. The clean message counts the files that were scanned, and the README lists the exit codes. Ledger: B123 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: stop requiring pip at install time pyproject.toml listed "pip>=25.3" as a runtime dependency. The package never imports or runs pip, but installing openadapt-privacy (or any package that depends on it) into an environment with an older pip upgraded the user's pip, and it conflicted with environments that pin pip on purpose. Remove pip from the runtime dependencies. Keep the pip>=25.3 floor in the one CI step that installs pip for the spaCy model download. A new test checks that the runtime dependencies and user extras do not list pip, setuptools, wheel, or uv. Ledger: B124 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the scan's fail-closed checks precise across Python versions - Treat a git permission warning as a failed listing only when it names a path in the tree. An unreadable global ignore file under $HOME no longer turns a complete scan into "Nothing was scanned" (exit 2). - Check each listed path with os.stat and raise on a permission error. On Python 3.14, Path.is_file() returns False instead, so files under a directory without search permission dropped out and the scan reported clean. - Exit 2, not a traceback, when the root's parent can't be searched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
openadapt-privacy-scanreported "PHI scan clean" and exited 0 when it hadn't read anything: a missing root, a root that's a file, an empty directory, and directories or files it couldn't open all passed the gate. The scan now exits 2 when it can't run and reports a file it can't read as a finding. This change also removespip>=25.3from the runtime dependencies, so installing the library no longer upgrades the user's pip.Fixes
--rootwas missing, was a file, or was an empty directory. It also skipped directories it couldn't list and files it couldn't open, and still reported clean.main()exits 2 with "PHI scan error: ... Nothing was scanned." when the root is missing, isn't a directory, or can't be accessed; when a directory under it can't be listed; or when there are no files to scan. A new--allow-emptyflag accepts an empty tree.os.walknow raises on an unreadable directory, and agit ls-filespermission warning about a path in the tree fails the listing (warnings about an unreadable global ignore file don't). Each listed path is checked withos.stat, so a permission error isn't read as "not a file" on Python 3.14. A file that can't be opened is reported asunreadable(exit 1).iter_scan_filesandscan_treeraiseOSErrorfor a missing or non-directory root. The README lists exit codes 0, 1, and 2.tests/test_scan.py:test_cli_missing_root_fails_closed,test_cli_file_root_fails_closed,test_cli_empty_root_fails_closed,test_cli_empty_root_passes_with_allow_empty,test_scan_tree_missing_root_raises,test_cli_unreadable_subdirectory_fails_closed,test_cli_unsearchable_subdirectory_fails_closed,test_cli_root_under_unsearchable_parent_fails_closed,test_cli_unreadable_file_is_a_finding,test_cli_unreadable_global_git_ignore_still_scanspip>=25.3was a runtime dependency, so installing openadapt-privacy (or a package that depends on it) upgraded the user's pip. The library never uses pip.pip>=25.3from[project].dependencies. The CI job that downloads the spaCy model, which is the only step that needs pip, installspip>=25.3itself.tests/test_packaging.py::test_runtime_and_user_extras_do_not_require_install_toolsNot fixed here
scan_tree()still returns an empty list for a tree with no files, because callers use it as a plain function, not a gate. The README now says to checkiter_scan_files(root)first when an empty tree should fail.piprequirement. They weren't checked in this change.How it was tested
test_cli_unreadable_global_git_ignore_still_scansfails onmain, where the scan exits 0, and passes on this branch. That test covers a casemainalready handles: it makes sure a warning about an unreadable global git ignore file doesn't stop a scan that listed every file.test_cli_unsearchable_subdirectory_fails_closedandtest_cli_root_under_unsearchable_parent_fails_closedalso pass on Python 3.14, wherePath.is_file()hides permission errors.main, where it findspip>=25.3, and passes on this branch. The built wheel's metadata has nopiprequirement.ruff checkpasses on the changed files. The 5ruff check .errors inscripts/generate_demo_images.py,tests/test_fixtures.py, andtests/test_loaders.pyare also onmain.uv buildproduces the sdist and wheel, andscripts/check_source_boundary.py --require-distpasses.Before merging
mainruns the release workflow, which publishes openadapt-privacy 1.1.1 to PyPI. Downstream packages stop pulling inpiponly after 1.1.1 is published.openadapt-privacy-scan. A missing, empty, or inaccessible--rootnow fails with exit 2 where it used to pass. Add--allow-emptyonly to a job that's expected to scan an empty tree.🤖 Generated with Claude Code