Skip to content

Commit 5d7e259

Browse files
Robcs01Botclaude
andcommitted
docs: promote Unreleased to 0.9.0 and fold in PR #2 cleanup
Required by release.py, which refuses to bump the version unless CHANGELOG.md already contains an entry for the new version. Folds the prior Unreleased feature work (anti-worm pre-pass, three new threat profiles, branch_name_regexes, persistence keywords, git-artifact scoring, fail-loud profile loading, regex-at-load-time) together with PR #2's clean-code cleanup (ScanContext, module-state removal, phase-3 hoist, function decomposition, report split, complete type annotations, mypy --strict tests/, Windows test portability). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 897ab27 commit 5d7e259

1 file changed

Lines changed: 16 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
# Changelog
22

3-
## Unreleased
3+
## 0.9.0 — 2026-05-15
44

55
### 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-
8-
### Changed
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.
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.
11-
12-
### Added
13-
- **`branch_name_regexes`** in `[ioc.git_artifacts]` — symmetric to `workflow_name_regexes`. Lets a profile match generated branch names that carry a timestamp or other variable component, e.g. Sha1-Hulud 2.0's `add-linter-workflow-<Date.now()>` dead-drop branches. Matches are deduplicated against the literal `branch_names` set so a branch covered by both forms emits a single finding. The `sha1-hulud-2025-11` profile now uses this to catch the 2.0-specific branch pattern.
6+
- **Anti-worm pre-pass** — single filesystem walk before the per-threat pipeline that discovers local git repositories and matches them against worm-class indicators aggregated from every loaded threat profile. Detects Shai-Hulud-style campaigns whose footprint is in `.git/description`, `.github/workflows/*.y[a]ml`, local branch names, and recent commit author emails. New modules: `git_repo_index.py`, `anti_worm_scanner.py`. New `[ioc.git_artifacts]` block in threat profiles (workflow filenames, regex patterns, branch names, commit author emails, repo descriptions). Per-repo corroboration scorer: workflow / description matches alone are HIGH; branch / author matches alone are LOW and escalate to HIGH when combined.
7+
- **Per-scan skip summary.** New `skip_report.py` module with a `SkipReport` instance 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.
8+
- **Shai-Hulud threat profile** (`shai-hulud-2025-09`) — covers the September 2025 npm worm wave anchored on `@ctrl/tinycolor@4.1.1` / `4.1.2`. Detects the `bundle.js` payload by SHA-256, the `truffleSecrets.json` / `cloud.json` / `actionsSecrets.json` / `contents.json` / `environment.json` / `format.json` staging dumps under `/tmp` (or `%TEMP%`), the `shai-hulud-workflow.yml` / `discussion.yaml` / `formatter_*.yml` backdoor workflows, the `Shai-Hulud` / `Sha1-Hulud` repo descriptions, the Dune-universe branch fingerprints, the `claude@users.noreply.github.com` commit author, and the `gh-token-monitor` host-side persistence daemon used by later waves.
149
- **Sha1-Hulud 2.0 threat profile** (`sha1-hulud-2025-11`) — November 2025 wave, anchored on `posthog-node` (4.18.1 / 5.11.3 / 5.13.3). Walk-files for `bun_environment.js` (three known SHA-256s) and `setup_bun.js` (one SHA-256). Adds `Sha1-Hulud: The Second Coming` / `Shai-Hulud: The Continued Coming` repo descriptions, `SHA1HULUD` self-hosted-runner keyword, and the Docker-sudoers privilege-escalation check in remediation guidance.
1510
- **Mini Shai-Hulud threat profile** (`mini-shai-hulud-2026-05`) — TanStack wave, anchored on `@tanstack/react-router` (1.169.5 / 1.169.8). Walk-files for `router_init.js` and `tanstack_runner.js` (hash-verified). Adds the `git-tanstack.com` typosquat exfil domain, the `Shai-Hulud: Here We Go Again` repo description, and the campaign-unique PBKDF2 salt `svksjrhjkcejg` as a persistence keyword. Remediation includes auditing Claude Code / VS Code extension manifests for the post-reboot persistence hooks the worm installs.
16-
- **Shai-Hulud threat profile** (`shai-hulud-2025-09`) — covers the September 2025 npm worm wave anchored on `@ctrl/tinycolor@4.1.1` / `4.1.2`. Detects the `bundle.js` payload by SHA-256, the `truffleSecrets.json` / `cloud.json` / `actionsSecrets.json` / `contents.json` / `environment.json` / `format.json` staging dumps under `/tmp` (or `%TEMP%`), the `shai-hulud-workflow.yml` / `discussion.yaml` / `formatter_*.yml` backdoor workflows, the `Shai-Hulud` / `Sha1-Hulud` repo descriptions, the Dune-universe branch fingerprints, the `claude@users.noreply.github.com` commit author, and the `gh-token-monitor` host-side persistence daemon used by later waves. Sha1-Hulud 2.0 (Nov 2025) and Mini Shai-Hulud (May 2026) are not yet shipped as separate profiles.
17-
- **Anti-worm pre-pass** — single filesystem walk before the per-threat pipeline that discovers local git repositories and matches them against worm-class indicators aggregated from every loaded threat profile. Detects Shai-Hulud-style campaigns whose footprint is in `.git/description`, `.github/workflows/*.y[a]ml`, local branch names, and recent commit author emails. New modules: `git_repo_index.py`, `anti_worm_scanner.py`. New `[ioc.git_artifacts]` block in threat profiles (workflow filenames, regex patterns, branch names, commit author emails, repo descriptions). Per-repo corroboration scorer: workflow / description matches alone are HIGH; branch / author matches alone are LOW and escalate to HIGH when combined.
11+
- **`branch_name_regexes`** in `[ioc.git_artifacts]` — symmetric to `workflow_name_regexes`. Lets a profile match generated branch names that carry a timestamp or other variable component, e.g. Sha1-Hulud 2.0's `add-linter-workflow-<Date.now()>` dead-drop branches. Matches are deduplicated against the literal `branch_names` set so a branch covered by both forms emits a single finding. The `sha1-hulud-2025-11` profile now uses this to catch the 2.0-specific branch pattern.
1812
- **`[ioc.persistence_keywords].terms`** — extra terms (e.g. standalone daemon names like `gh-token-monitor`) that the persistence scanner matches in addition to the package name. Catches payloads whose persistence artifacts don't carry the parent package's name.
1913
- New `FindingCategory.GIT_ARTIFACT`; scoring rules updated so a git artifact alone yields MEDIUM and version + git artifact yields HIGH.
14+
- `ruff` and `mypy` declared as dev dependencies.
2015

2116
### Changed
22-
- `search_roots._deduplicate_roots` renamed to `deduplicate_roots` (now used by the anti-worm pre-pass to union ecosystem roots).
17+
- **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.
18+
- **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.
19+
- **Strict unknown-key detection in threat-profile TOML.** Typos like `commands` instead of `command` no longer silently produce a half-loaded profile.
20+
- **Hardened ss / lsof IPv6 peer parsing** to handle bracketed-port and zone-id forms correctly.
21+
- **Internal scan plumbing simplified.** `ScanContext` dataclass collapses the prior 6-parameter clump across phase signatures. Module-level mutable state eliminated: `SkipReport` now plumbed explicitly, `_ecosystem_cache` replaced with `functools.lru_cache`, autouse reset fixture removed. Phase-3 IOC dispatch hoisted out of `ioc_scanner` into `scanner._run_phase3_iocs`. Long functions decomposed (`scanner.main`, `_scan_for_c2_connections`, `_scan_walk_files`, `scan_source_and_configs`); `main()` now returns an `int`. The 369-line `report.py` split into a `report/` package with five sub-modules. Lazy `_user_threat_dir()`. `search_roots._deduplicate_roots` renamed to `deduplicate_roots`.
22+
- **Walker / reader skips routed through `SkipReport` consistently**, and NXDOMAIN coverage surfaced on `--resolve-c2`.
23+
- **Complete type annotations** on every public entry point and on every plumbed `skip_report` parameter. The production package and the test suite both pass `mypy --strict` (37 + 27 source files, 0 errors).
24+
- **Cross-platform test portability.** The `os.geteuid()` skipif guards now wrap a `hasattr(os, "geteuid")` check so test collection no longer crashes on Windows. The anti-worm corroboration test compares against `str(Path(...))` rather than literal `/repo/A` strings, matching the scanner's actual output on every OS. The `_check_tmp_scripts` test class is explicitly marked POSIX-only, mirroring the scanner's own `sys.platform != "win32"` guard.
25+
26+
## 0.8.2 — 2026-04-02
2327

2428
## 0.8.2 — 2026-04-02
2529

0 commit comments

Comments
 (0)