You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Walking system roots as a non-root user routinely hits inaccessible
sub-trees and unreadable files. Each occurrence is correct
best-effort behaviour, but in aggregate the operator should see how
much of the filesystem was actually inspected — a partial scan from
missing privileges is not a clean scan.
New `skip_report.py` module with a `SkipReport` singleton that the
walk / read helpers append to whenever they swallow PermissionError
or OSError. Renderer in report.py prints a yellow block at the end
of the scan listing the first few paths in each category and the
total counts, with a hint to re-run with elevated privileges. Silent
when no paths were skipped.
Instrumented sites:
* config.pruned_walk — now uses os.walk's onerror callback so
depth-N permission denials are recorded, not just top-level
root denials (os.walk silently drops per-subdirectory errors
without onerror set).
* config.read_if_contains
* git_repo_index._find_repo_roots (same onerror treatment) and
its targeted file readers (description, refs/heads, packed-refs,
workflow files).
* persistence_scanner (config-dir glob, shell-rc read, /tmp iter).
* ioc_scanner (known-paths exists check, walk-files hash-read).
Intentionally not instrumented:
* subprocess_utils.run_safe — tool-availability is already loud
where the operator needs to know.
* network_scanner /proc/$PID/exe reads — PermissionError there is
the normal case for other users' PIDs and would flood the
summary on any multi-user host.
* json.JSONDecodeError / ast.SyntaxError on third-party files —
different category (parse failure of code we don't own), not
a "skipped path" in the user-facing sense.
Test isolation: new autouse fixture in conftest.py resets the
singleton before and after every test. Live PermissionError tests
guard with `pytest.skip` when running as root, since POSIX
permission bits don't apply.
Smoke test on a real Linux machine: 65 paths recorded (mostly
/opt/containerd, /var/cache/*, /var/log/journal/*) — all expected
for a non-root scan.
408 tests pass (was 398).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,9 @@
2
2
3
3
## Unreleased
4
4
5
+
### Added
6
+
-**Per-scan skip summary.** New `skip_report.py` module with a `SkipReport` singleton that the walk / read helpers append to whenever they swallow a `PermissionError` or `OSError`. Instrumented sites: `config.pruned_walk` (with `onerror` callback so depth-N denials are caught, not just the top-level root), `config.read_if_contains`, `git_repo_index._find_repo_roots` and its targeted file readers, `persistence_scanner` (config-dir, shell-rc, /tmp iteration), and `ioc_scanner` (known-paths exists check + walk-files hash-read). A new `print_skip_summary` block at the end of the report tells the operator how many paths were not inspected and shows the first few of each category, so a partial scan from missing privileges is no longer indistinguishable from a clean scan. Silent when no paths were skipped. Test isolation handled by an autouse fixture in `tests/conftest.py` that resets the singleton between tests.
7
+
5
8
### Changed
6
9
-**Fail loud on malformed threat profiles.**`_load_from_dir` no longer swallows `KeyError` / `tomllib.TOMLDecodeError` / `re.error` from individual profiles. Any broken TOML — missing required field, syntax error, or invalid regex — now raises `InvalidThreatProfileError` with the offending file path and stops the scan. A user who wrote a profile expects it to be active; silently logging at WARNING and continuing was the kind of stealth-failure mode that §8 (Fail Fast, Fail Loud) explicitly warns against.
7
10
-**Regex compilation moved from scan time to load time.**`GitArtifactsIOC.workflow_name_regexes` and `branch_name_regexes` are now `tuple[re.Pattern[str], ...]` (was `tuple[str, ...]`). `_parse_git_artifacts` compiles each pattern; a malformed pattern raises `re.error` with the field name and the offending pattern. The `aggregate_indicators` function no longer needs `_compile_into` or any exception handling — patterns are pre-validated.
0 commit comments