Commit Graph
100 Commits
Author SHA1 Message Date
Justin BollingerandClaude Opus 4.7 d096cd53a8 fix(pipal): preserve newline on $HEX[...] cracked rows
binascii.unhexlify().decode() returned bytes without the trailing newline
that normal rows inherit from password[-1], so HEX-encoded cracked
passwords concatenated with the next entry in the .passwords file fed to
pipal. Pipal under-counted entries and ranked corrupted mashups as base
words. Re-append \n if missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 12:02:17 -04:00
Justin Bollinger c8adb79249 Merge branch 'dev' into main 2026-05-28 11:44:46 -04:00
Justin Bollinger 4f445fdf2b Merge branch 'fix/pushover-notification-name-mismatches' into dev 2026-05-28 11:44:43 -04:00
Justin BollingerandClaude Opus 4.7 5fcd589cbc docs: add v2.10.4 version history entry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:44:06 -04:00
Justin BollingerandClaude Opus 4.7 b5390b1b0f fix(notify): align attack name passed to _should_fire with prompt name (#110)
Pushover notifications were silently dropped for Quick Crack, Loopback,
Combinator, PRINCE-LING and N-gram because the handlers prompted under
one name (e.g. "Quick Crack") while the underlying hashcat wrapper
fired with a different label ("Quick Dictionary"). _should_fire keyed
the per-run consent on the prompt name, so the consent check at fire
time always missed.

Threaded the prompt name down to _run_hcat_cmd by:
- Adding attack_name kwargs to hcatQuickDictionary and hcatPrince, the
  two wrappers shared by multiple user-facing handlers.
- Relabelling hcatCombination/Combinator3/CombinatorX (all reach the
  user via combinator_crack which prompts "Combinator") and hcatNgramX
  (single caller: ngram_attack prompting "N-gram").
- Updating quick_crack, loopback_attack, and hcatPrinceLing to pass the
  matching attack_name override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:40:26 -04:00
Justin Bollinger 731fb21019 Merge branch 'dev' into main 2026-05-26 17:06:09 -04:00
Justin Bollinger 3adb1ac37d Merge branch 'bugfix/auto-update-branch-switch' into dev 2026-05-26 16:48:48 -04:00
Justin BollingerandClaude Opus 4.7 32ca52af77 docs: add v2.10.3 version history entry
Covers the auto-upgrade-loop fix shipping in this patch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:31:22 -04:00
Justin BollingerandClaude Opus 4.7 74d665e6ee fix(update): switch to main before pull to stop upgrade loop on dev
Symptom: user on the `dev` branch upgrading from v2.10.0 to v2.10.2 saw
the "Upgrade now? [y/N]" prompt fire on every restart. Accepting it ran
the install pipeline successfully but the next launch re-prompted the
same way.

Root cause: release tags in this repo live on main-side merge commits
(e.g. v2.10.2 is on the `Merge branch 'dev' into main` commit). That
commit is *downstream* of `dev`, so walking back from dev's HEAD,
setuptools-scm finds only v2.10.0 and reports e.g. `2.10.0.post1.dev9`.
After hate_crack/__init__.py strips the post/dev suffix, `__version__`
is `"2.10.0"` again. check_for_updates() sees `2.10.2 > 2.10.0` and
re-prompts forever. `git pull` on dev is a no-op because dev is fully
synced — the tag just isn't on the branch.

Fix: _run_upgrade() now detects the current branch via `git symbolic-ref
--short HEAD`. If it's not `main`, refuse to clobber uncommitted work
(git status --porcelain), then `git checkout main` before running the
existing pull/install pipeline. The release-tagged commit is now on
HEAD, setuptools-scm regenerates a clean version, and the loop ends.

Safety guards:
  - Uncommitted changes: bail with manual-steps message (exit 1) — no
    git checkout attempted.
  - main checked out in another worktree: surface git's stderr and bail
    with manual instructions.
  - Detached HEAD (symbolic-ref returncode != 0): skip the switch and
    let the existing flow run — no regression for users on a tag.
  - Already on main: skip the switch block; behavior unchanged.

Tests (tests/test_version_check.py):
  - test_run_upgrade_switches_from_dev_to_main_then_upgrades
  - test_run_upgrade_bails_when_non_main_branch_is_dirty
  - test_run_upgrade_bails_when_checkout_main_fails
  - test_run_upgrade_skips_switch_when_already_on_main
  - test_run_upgrade_skips_switch_on_detached_head

Four pre-existing tests updated to thread an extra branch_proc mock
through the new symbolic-ref subprocess call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:31:14 -04:00
Justin Bollinger ffa6577da2 Merge branch 'dev' into main 2026-05-26 15:10:07 -04:00
Justin BollingerandClaude Opus 4.7 19abb7b479 docs: add v2.10.2 version history entry
Covers the two fingerprint-attack bugfixes shipping in this patch:
the empty-.expanded guard and the LC_ALL=C sort locale fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:09:05 -04:00
Justin Bollinger 3b4a0cbd3f Merge branch 'bugfix/sort-illegal-byte-sequence' into dev 2026-05-26 15:04:46 -04:00
Justin BollingerandClaude Opus 4.7 51083c876c fix: force LC_ALL=C for sort -u subprocesses to handle non-UTF-8 bytes
macOS `sort` is locale-strict: under LC_COLLATE=en_US.UTF-8 (the default
on most macOS shells) it errors out with "sort: Illegal byte sequence"
when stdin contains bytes that are not valid UTF-8. Cracked-password
streams routinely contain such bytes - hex-encoded fields, mixed
encodings, binary garbage from poorly-encoded source hashes - so this
fires in real fingerprint runs whenever the pot already has any non-
ASCII output.

Symptom in the fingerprint attack: the expander -> sort pipeline emits
"sort: Illegal byte sequence" and produces an empty .expanded file. The
empty-.expanded guard added in the previous patch then triggers the
"no candidates to expand" skip message - which is misleading, because
the user does have cracks; they just got dropped on the sort step.

Pass env={**os.environ, "LC_ALL": "C"} to all three subprocess.Popen
calls that invoke `sort -u`:
  - _write_field_sorted_unique  (main.py:1163)
  - hcatFingerprint expander    (main.py:1544)
  - hcatLMtoNT combinator dedupe (main.py:2995)

LC_ALL=C makes sort byte-collation only. Dedup correctness is
unaffected (byte equality is locale-independent), and hashcat doesn't
care about wordlist order.

Also adds an AST-level test that fails if any future `sort` Popen lacks
an env kwarg, so the locale fix can't silently regress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:04:42 -04:00
Justin Bollinger 83c1012473 Merge branch 'bugfix/fingerprint-empty-expanded-guard' into dev 2026-05-26 14:57:33 -04:00
Justin BollingerandClaude Opus 4.7 5dd32fe6c1 test: repair pre-existing flakes in cli_flags, fingerprint iter, submodule
Three unrelated test issues were masking the fingerprint regression
above and need to be green for the suite to be trustworthy:

1. tests/test_cli_flags.py: 7 tests monkeypatched input() to return "5",
   but in the no-hashfile main menu "5" enters the Wordlist Tools
   submenu and then loops forever on "Split by Length" / "File not
   found". Changed to "7" (Exit), the documented exit option.

2. tests/test_fingerprint_expander_and_hybrid.py: the iter-based
   lineCount mock (iter([1,1,1])) raised StopIteration because
   _run_hcat_cmd now also calls lineCount once per invocation when
   notifications fire. Replaced with a constant `lambda _p: 1` so the
   test no longer couples to internal call counts.

3. tests/test_submodule_hashcat_utils.py: `git submodule update --init`
   exits 0 in git worktrees but does not populate submodule
   directories. The test failed environmentally for anyone running it
   from a worktree. After the init attempt, if the dir is still empty,
   pytest.skip with a clear message rather than fail - preserves the
   original intent for normal checkouts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:57:22 -04:00
Justin BollingerandClaude Opus 4.7 6040460919 fix(fingerprint): skip hashcat when expanded wordlist is empty
When no hashes have been cracked yet, the fingerprint attack's expander
pipeline produces an empty {hash}.expanded file. Previously the function
would still launch a hashcat combinator session against two empty
wordlists (and, when invoked via the menu, six more hashcat sessions
from the secondary hybrid pass) - wasting cycles and creating confusing
empty intermediate files.

Add an early-exit guard after the expander pipeline: if .expanded is
empty, print an informative skip message and break out of the loop
before invoking hashcat or hcatHybrid.

Mirrors the existing "if hcatNewPasswords > 0" guard pattern in
hcatRecycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:57:11 -04:00
Justin Bollinger aa2ec88c48 Merge branch 'dev' into main 2026-05-05 19:45:21 -04:00
Justin Bollinger 5fc54f4574 docs: add v2.10.1 version history entry 2026-05-05 19:45:14 -04:00
Justin Bollinger 5144bac72a Merge branch 'chore/bump-hashcatrosetta' into dev 2026-05-05 19:44:02 -04:00
Justin Bollinger de1d75f8c9 chore(deps): bump HashcatRosetta to v0.2.0, add click runtime dep
Resolves Dependabot alert #1 — vulnerable pytest tmpdir handling
(< 9.0.3) in HashcatRosetta/requirements.txt. Upstream v0.2.0
drops pytest from runtime requirements and now depends on click
for its CLI/formatting module, so add click>=8.0.0 to hate_crack
runtime deps to keep display_rule_opcodes_summary functional.
2026-05-05 19:41:21 -04:00
Justin Bollinger 6e282b0c03 Merge branch 'dev' into main 2026-05-05 18:51:14 -04:00
Justin Bollinger 317222b5b7 Merge branch 'bugfix/hashview-download' into dev 2026-05-05 18:51:09 -04:00
Justin BollingerandClaude Sonnet 4.6 589297c991 test(hashview-download): strengthen assertions, add NTLMv2 rsplit coverage
- Strengthen left-file assertions to verify hash-only lines and exclude passwords
- Add test_download_left_rsplit_ntlmv2 to verify rsplit(":", 1) correctly handles
  multi-colon hashes like NTLMv2 potfile format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 18:36:47 -04:00
Justin Bollinger f614505c96 fix(hashview-download): append found hashes to left file, fix rsplit for NTLMv2 2026-05-05 17:47:52 -04:00
Justin BollingerandClaude Sonnet 4.6 9ec56d4ead feat(wordlist-optimize): accept directories as input, inline tab-completion display, add Wordlist/Rule Tools to main menu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:42:56 -04:00
Justin BollingerandClaude Sonnet 4.6 60ae6656ad fix(wordlist-optimize): use rli.bin (hash-set) instead of rli2.bin (sorted merge-join)
wordlist_optimize worker called wordlist_subtract_single (rli2.bin), which
requires both files to be lexicographically sorted. splitlen.bin preserves
insertion order, so rli2.bin silently missed duplicates. Switch to
wordlist_subtract (rli.bin), which loads the remove-list into a hash set and
is order-independent. Also add "(comma-separated)" to the input paths prompt.
Update worker tests to patch wordlist_subtract instead of wordlist_subtract_single.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 21:26:47 -04:00
Justin BollingerandClaude Sonnet 4.6 0d3282654b fix(wordlist-optimize): remove allow_multiple crash, redundant import, add worker tests
- Remove `allow_multiple=True` from wordlist_optimize handler to avoid
  AttributeError when the return value is a list rather than a str
- Remove redundant `import tempfile` inside wordlist_optimize worker body
  (tempfile is already imported at module level on line 29)
- Add TestWordlistOptimizeWorker with 6 tests covering: fast-path
  (empty outdir), merge-path (subtract+append), new-length copy,
  splitlen failure, subtract failure, and missing-input skip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 21:20:53 -04:00
Justin BollingerandClaude Sonnet 4.6 cf12c3264e feat(wordlist-tools): add Optimize Wordlists submenu option (8)
Adds wordlist_optimize worker to main.py that consolidates multiple
wordlists into per-length deduplicated files using splitlen.bin and
rli2.bin. Wires handler in attacks.py and registers it as option 8 in
the Wordlist Tools submenu. Adds 16 tests covering happy path, empty
input, missing files, empty outdir, failure path, and submenu dispatch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 21:13:10 -04:00
Justin BollingerandClaude Sonnet 4.6 0435314043 fix(menu): plain numbered menu by default with aligned key columns
- Switch default from simple-term-menu (arrow-key) to plain numbered
  input so all keys including 10+ can be selected by typing a number;
  arrow-key mode is now opt-in via HATE_CRACK_ARROW_MENU=1
- Right-align key numbers within brackets so single- and multi-digit
  items line up: [ 1] through [99]
- Use [key] format consistently in both plain and arrow-key modes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 18:38:03 -04:00
Justin BollingerandClaude Sonnet 4.6 14c6edf27a refactor(menu): close numbering gap, renumber attacks 13-24 → 10-21
Menu options jumped from 9 to 13 because items 10-12 were removed
historically but remaining items kept their old numbers. Renumbers
the contiguous attack block to 10-21, closing the gap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 18:37:57 -04:00
Justin BollingerandClaude Sonnet 4.6 eacbc44042 fix(attacks): support range syntax in rule selection (e.g. 138-141)
Rule numbers entered as N-M now expand to all integers in that range
before lookup, matching user expectation that 138-141 selects rules
138 through 141 sequentially. Also catches ValueError alongside
IndexError when a token cannot be converted to int.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 18:37:10 -04:00
Justin Bollinger 8de53b6888 Merge feature/pcfg-integration: PCFG and PRINCE-LING attacks 2026-05-04 09:18:59 -04:00
Justin Bollinger 80105e8716 test(pcfg): add UI menu option tests for #23 and #24 2026-05-04 09:11:52 -04:00
Justin Bollinger 0563f5bd30 feat(pcfg): register PCFG attacks in hate_crack.py menu (dual-registration) 2026-05-04 09:10:46 -04:00
Justin Bollinger 7173af241e feat(pcfg): add pcfg_attack and prince_ling_attack handlers in attacks.py 2026-05-04 09:09:39 -04:00
Justin Bollinger 8d575f0bb2 feat(pcfg): wire menu entries #23 (PCFG) and #24 (PRINCE-LING) in main.py 2026-05-04 09:08:17 -04:00
Justin Bollinger 6f30cbbccc feat(pcfg): add pcfg and prince_ling dispatchers in main.py 2026-05-04 09:07:07 -04:00
Justin Bollinger 629ca85023 fix(pcfg): catch OSError on cache replace; tighten exception-restore test 2026-05-04 09:06:31 -04:00
Justin BollingerandClaude Sonnet 4.6 1718d55759 feat(pcfg): add hcatPrinceLing attack (mode B — cached wordlist via prince_ling, delegates to hcatPrince)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 09:04:15 -04:00
Justin Bollinger 2ee0123b00 test(pcfg): tighten hcatPCFG test — patch globals, assert stdin pipe, drop unused 2026-05-04 09:01:57 -04:00
Justin Bollinger e7410e8404 fix(pcfg): revert generate_session_id signature change, patch in test instead 2026-05-04 08:56:32 -04:00
Justin BollingerandClaude Sonnet 4.6 61ee3b50d7 feat(pcfg): add hcatPCFG attack (mode A — pcfg_guesser piped to hashcat)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 08:53:12 -04:00
Justin Bollinger ca124189e4 feat(pcfg): verify pcfg_cracker presence at startup (non-fatal) 2026-05-04 08:48:56 -04:00
Justin Bollinger 4b13063d7a feat(pcfg): load pcfg config keys with sensible defaults 2026-05-04 08:47:04 -04:00
Justin Bollinger 813399d209 feat(pcfg): add pcfgRuleset, pcfgMaxCandidates, pcfgPrinceLingMaxCandidates config keys 2026-05-04 08:45:09 -04:00
Justin Bollinger aea32b9268 feat(pcfg): vendor pcfg_cracker as submodule 2026-05-04 08:43:05 -04:00
Justin Bollinger d7fbf3de17 Merge branch 'tui-default-dep' into dev 2026-05-03 14:37:13 -04:00
Justin BollingerandClaude Opus 4.7 202f5480e1 build(deps): make simple-term-menu a default dependency
Move simple-term-menu from the [tui] optional extra into the main
dependencies list so arrow-key menu navigation works out of the box.
Users can still opt into the plain numbered menu with
HATE_CRACK_PLAIN_MENU=1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 14:37:08 -04:00
Justin BollingerandClaude Sonnet 4.6 90257cfb1b fix(docker): fix E2E torrent test and transmission-remote compatibility
- Add transmission-cli package (provides transmission-remote binary)
- Fix SETUPTOOLS_SCM_PRETEND_VERSION placement before RUN make install
- Fix PATH to use /root/.local/bin and include venv bin
- Switch TransmissionSession.add() from watch-dir polling to transmission-remote -a
- Remove --no-auth flag (unrecognized in transmission-remote 4.1.0)
- Add test_docker_torrent_downloads_wordlists E2E test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 22:56:13 -04:00
Justin BollingerandClaude Sonnet 4.6 c037242037 docs(readme): correct version history to v2.9.3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:41:45 -04:00
Justin BollingerandClaude Sonnet 4.6 a2a79a72b4 docs(readme): consolidate torrent changes under v2.10.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:38:51 -04:00
Justin BollingerandClaude Sonnet 4.6 598bfd97e5 fix(api): store torrent files in /tmp/hate_crack/ not /tmp/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:28:22 -04:00
Justin BollingerandClaude Sonnet 4.6 0d8089ea17 fix(api): increase watch-dir polling timeout from 10s to 30s
Transmission's watch dir scanner runs every ~10s, so a 10s deadline
could expire before the first scan completes. 30s gives 2-3 scan
cycles of headroom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:27:33 -04:00
Justin BollingerandClaude Sonnet 4.6 7e8641bc3b docs(readme): add v2.9.3 version history entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:08:55 -04:00
Justin BollingerandClaude Sonnet 4.6 998aa93e24 feat(api): route torrent files through daemon watch dir, not wordlist dir
- Suppress transmission-daemon stdout/stderr (background process)
- Create a watch/ subdir in the session temp config dir; start daemon
  with --watch-dir instead of --no-watch-dir
- Replace transmission-remote -a with shutil.copy2 into the watch dir,
  then poll until the daemon picks it up
- Save .torrent metadata files to tempfile.gettempdir() instead of the
  wordlist directory; update cleanup to match
- Update TransmissionSession.add tests to cover the new watch-dir flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:06:45 -04:00
Justin BollingerandClaude Sonnet 4.6 894a565a7f fix(update): force version rebuild after upgrade to stop update loop
After make install, run uv sync --reinstall-package hate_crack so
setuptools-scm regenerates the version from the new git tag. Without
this, the installed version stays at the old value and the update
checker loops indefinitely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:46:58 -04:00
Justin BollingerandClaude Sonnet 4.6 44167953ea fix(api): use --port instead of --rpc-port for transmission-daemon
transmission-daemon 4.x uses --port for the RPC port, not --rpc-port.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:41:46 -04:00
Justin BollingerandClaude Sonnet 4.6 57181ddf13 refactor(update): replace custom dep install with make install
_run_upgrade now runs `git pull && git fetch --tags && make install`
instead of the bespoke _install_system_deps function. make install is
idempotent, handles all platforms, installs system deps (transmission-
daemon, p7zip), rebuilds the Python package, and updates the CLI shim.

Remove the now-unnecessary uv binary pre-check (make install locates
it) and its stale test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:40:43 -04:00
Justin BollingerandClaude Sonnet 4.6 e238c9bb66 feat(update): install system deps (transmission-daemon/p7zip) on upgrade
Add _install_system_deps() called by _run_upgrade() after a successful
git pull. Installs transmission-daemon (Linux) or transmission-cli brew
formula (macOS) so users who upgrade don't need to re-run make install
manually. Also corrects _run_upgrade to exit 1 on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:14:53 -04:00
Justin Bollinger f43b0742a1 Merge branch 'feat/auto-detect-usernames' 2026-04-25 19:09:26 -04:00
Justin BollingerandClaude Sonnet 4.6 56b2f99145 fix(api): add missing return in info_file after exhausted loop
Prevent implicit None return when no data row matches the expected
transmission-remote --info-files format. Also clarify README macOS
install note.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 17:31:31 -04:00
Justin BollingerandClaude Sonnet 4.6 240cb0e5b2 fix(api): correct brew formula in check_transmission_daemon message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:14:31 -04:00
Justin BollingerandClaude Sonnet 4.6 dc62af105a fix(docs): use correct brew formula transmission-cli, clarify both deps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:12:53 -04:00
Justin BollingerandClaude Sonnet 4.6 cca2ff0065 chore: replace transmission-cli with transmission-daemon in CI/install
Delete wordlists/kill_transmission.sh (replaced by Python extraction).
Update Makefile, Dockerfile.test, lima config, README, TESTING.md, and
test_dependencies.py to reference transmission-daemon/transmission-remote
instead of transmission-cli.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 00:46:51 -04:00
Justin BollingerandClaude Sonnet 4.6 6d4c7b7da4 fix(api): resolve save_dir once and report total failure in weakpass menu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 00:43:24 -04:00
Justin BollingerandClaude Sonnet 4.6 1c3607b8a8 fix(main): wire --download-all-torrents to fetch_torrent_metadata
Pass fetch_torrent_metadata (HTTP-only) to download_all_weakpass_torrents
so the batch collects all .torrent files first, then runs exactly one
TransmissionSession rather than N single-torrent sessions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 00:37:25 -04:00
Justin BollingerandClaude Sonnet 4.6 e51f980168 refactor(api): wire entry points through TransmissionSession
Split download_torrent_file into fetch_torrent_metadata (HTTP-only) and
a backwards-compat shim. Update weakpass_wordlist_menu,
download_all_weakpass_torrents, and download_weakpass_torrent to collect
.torrent files first, then run a single TransmissionSession batch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 00:36:05 -04:00
Justin BollingerandClaude Sonnet 4.6 6064b79fbd fix(api): correct TransmissionSession edge cases from code review
- add() now diffs before/after list() for fallback ID detection
- remove unreachable return in info_file
- simplify wait_for_all break logic
- always call on_complete even when info_file returns empty string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 00:31:56 -04:00
Justin BollingerandClaude Sonnet 4.6 9b6de8da71 feat(api): add TransmissionSession daemon infrastructure
Replace check_transmission_cli with check_transmission_daemon, add
_pick_free_port, TransmissionSession context manager, and
run_torrent_session. A single transmission-daemon process handles all
selected torrents in parallel and tears down via transmission-remote
--exit on completion or interrupt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 00:24:39 -04:00
Justin BollingerandClaude Sonnet 4.6 4cfb67e2b9 fix(tests): update test_download_keyboard_interrupt for new re-raise behaviour
download_official_wordlist now propagates KeyboardInterrupt (via
_streamed_download) so callers like list_and_download_official_wordlists
can catch it and return to the menu. The old test expected False to be
returned, which no longer matches the intended design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:48:05 -04:00
Justin BollingerandClaude Sonnet 4.6 72b2e95b38 test(api_downloads): fix patch namespaces and vacuous assertion
Fix two test issues:
1. Patch time.sleep in hate_crack.api namespace to properly mock the module import (lines 384, 403). Tests were patching the global time.sleep, allowing real sleeps to execute and making tests slow.
2. Remove vacuous assertion in test_meta_refresh_redirect_uses_verbatim_url (line 440) that is always True due to URL domain mismatch. The equality assertion above it already covers the correctness requirement.

These changes speed up TestHashmobBackoff tests from slow to instant, and remove misleading logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:46:14 -04:00
Justin BollingerandClaude Sonnet 4.6 257db8d044 test(api): add TestStreamedDownload, TestHashmobBackoff, redirect-bug, and skip-existing tests
Covers the four new private helpers (_stream_response_to_file,
_streamed_download, _with_hashmob_backoff, _Hashmob429) and the
list_and_download_official_wordlists skip-existing path (10 new tests,
40 total in the file).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:44:06 -04:00
Justin BollingerandClaude Sonnet 4.6 698c28467e fix(api): move q.task_done() to finally block in Weakpass worker
The worker() function inside fetch_all_weakpass_wordlists_multithreaded()
called q.task_done() inside the except block. If the except block itself
throws (e.g., KeyboardInterrupt during print), task_done() is skipped
and q.join() hangs forever.

Move task_done() to a finally block to ensure it always runs, allowing
the queue manager to correctly track completion even if error handling
itself fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:40:29 -04:00
Justin BollingerandClaude Sonnet 4.6 03a70c01b3 fix: auto-detect Weakpass total_pages instead of hardcoding 67
Replace the hardcoded `total_pages=67` default with `None` (auto-detect).
On first call the function probes page 1 to read `last_page` from the
Inertia `data-page` payload; if found it drives the thread pool with that
count, if not found it falls back to a sequential walk until an empty page
is returned, and if the probe itself fails it degrades gracefully to 67.
Callers that pass `total_pages` explicitly are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:38:24 -04:00
Justin BollingerandClaude Sonnet 4.6 979953cf45 feat(api): skip already-present wordlists in list_and_download_official_wordlists
Before each download in both the 'all' and indexed-selection branches,
check whether the destination file already exists and has nonzero size
(accounting for .7z archives whose extracted name drops the suffix).
Mirrors the same guard already in place for rule downloads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:37:58 -04:00
Justin BollingerandClaude Sonnet 4.6 11b575d5a6 fix(api): skip final sleep in backoff loop, fold probe into stream, guard resp.close
Fix 1: _with_hashmob_backoff no longer sleeps after the last attempt —
breaks out of the loop immediately so we don't waste up to 300s before
giving up.

Fix 2: download_hashmob_wordlist and download_hashmob_rule now open a
single streaming connection inside _attempt() and pass the already-open
response to _stream_response_to_file, eliminating the probe-then-reopen
pattern that caused 429 responses on the second request to bypass the
backoff retry machinery.

Fix 3: HashviewAPI.download_wordlist wraps _stream_response_to_file in
try/finally so resp.close() is guaranteed even on KeyboardInterrupt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:35:27 -04:00
Justin BollingerandClaude Sonnet 4.6 d11194cf6e refactor(api): consolidate streaming-download logic into shared helpers
Add _stream_response_to_file and _streamed_download helpers that
implement atomic .part-file writes, unified progress bars, and
KeyboardInterrupt cleanup in one place.  Add _with_hashmob_backoff
for bounded 429 retry logic (max 6 attempts, step-doubling delay)
and _Hashmob429 sentinel exception so callers signal rate-limits
without re-implementing the backoff loop.

Refactor all four callers onto the helpers:
- download_hashmob_wordlist: removes inline backoff loop and fixes
  the redirect recursion bug (redirect URL was passed as file_name,
  bypassing the API prefix logic)
- download_hashmob_rule: removes duplicate backoff loop; keeps the
  60-entry pinned URL dict and 404→alt_url fallback verbatim
- download_official_wordlist: delegates streaming to _streamed_download;
  keeps .7z extraction tail
- HashviewAPI.download_wordlist: uses session.get + _stream_response_to_file
  so cookie auth is preserved; removes the separate progress-bar
  implementation

All public signatures are unchanged; 40 existing tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:30:25 -04:00
Justin BollingerandClaude Opus 4.7 9eb584cda4 Merge branch 'feat/notifications-submenu' into feat/pushover-notifications
Consolidates Pushover notification menu options under a new submenu
at main-menu option 82, and promotes notify_per_crack_enabled from
config-file-only to a runtime toggle with a UI-level guard that
refuses to enable it while global notifications are OFF.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:37:23 -04:00
Justin BollingerandClaude Opus 4.7 c00436d73e chore: add .git-blame-ignore-revs with style commit
Isolates blame churn from the ruff format pass in commit 4fa43a7,
which reformatted pre-existing lines in hate_crack/main.py that are
outside the scope of the notifications-submenu feature.

Enable locally with:
  git config blame.ignoreRevsFile .git-blame-ignore-revs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:23:05 -04:00
Justin BollingerandClaude Opus 4.7 31e4f38eb3 test(notify): cover submenu label refresh; document inline-import rationale
Added `test_labels_refresh_between_iterations` that sequences a toggle
then captures the submenu items twice, asserting the label flips
between renders. Guards against a regression where `items` is hoisted
out of the while-loop.

Also documented why the inline `from hate_crack.menu import
interactive_menu` is not actually redundant with the module-scope
import at main.py:77 — it re-reads the attribute on every call, which
is what lets tests patch `hate_crack.menu.interactive_menu`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:22:43 -04:00
Justin BollingerandClaude Sonnet 4.6 4fa43a79f4 style: ruff format pass for Notifications submenu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:32:44 -04:00
Justin BollingerandClaude Sonnet 4.6 61fb8309d2 docs: document Notifications submenu (option 82) in README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:28:08 -04:00
Justin BollingerandClaude Sonnet 4.6 e33abc89f4 feat(notify): move options 83/84 under new Notifications submenu (82)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:07:17 -04:00
Justin BollingerandClaude Sonnet 4.6 43773fa054 feat(notify): add Notifications submenu dispatcher
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:02:37 -04:00
Justin BollingerandClaude Sonnet 4.6 6b3d4e7e77 feat(notify): add per-crack UI toggle with global-OFF guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 18:57:21 -04:00
Justin BollingerandClaude Sonnet 4.6 e644e54b4b feat(notify): add toggle_per_crack_enabled runtime toggle
Promotes notify_per_crack_enabled from config-file-only to a runtime
toggle with the same style (global-decl, default-init, OSError-via-logger)
as the existing toggle_enabled, with full TDD coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 18:52:00 -04:00
Justin BollingerandClaude Sonnet 4.6 8a148d5261 feat(notify): persist notify_per_crack_enabled atomically
Add save_per_crack_enabled() as a data-layer sibling to save_enabled(),
using the same _atomic_rewrite primitive so mid-write crashes cannot
corrupt config.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 18:48:24 -04:00
Justin Bollinger eae45517b3 Merge branch 'feat/test-pushover-menu' into feat/pushover-notifications
Adds menu option 84 (Send Test Pushover Notification) so users can verify
their Pushover credentials without running an attack. Ignores the global
notify_enabled toggle by design (prints a note when OFF).
2026-04-22 18:29:05 -04:00
Justin Bollinger 589259ebaf test(notify): cover option 84 in menu wiring parametrize 2026-04-22 18:15:09 -04:00
Justin Bollinger 6d0e9a154f feat(notify): wire option 84 into hate_crack.py proxy menu 2026-04-22 18:13:31 -04:00
Justin Bollinger 36115a75f2 feat(notify): wire option 84 into main.py menu 2026-04-22 18:11:35 -04:00
Justin Bollinger 16bdfe7d52 fix(tests): patch hate_crack.main._notify directly
tests/test_random_rules_attack.py purges and re-imports hate_crack.*
modules, which leaves main._notify pointing at a different notify
object than a top-level patch("hate_crack.notify._send_pushover")
would touch. Under the full suite that caused the test's mock to
miss and the production call to hit the real Pushover API.

Switch to patch.object(hc_main, "_notify") -- same pattern as
tests/test_run_hcat_cmd.py but anchored to the exact module object
already bound as hc_main, so it is immune to sys.modules churn
regardless of import order. Drop the now-redundant _install_settings
helper and _reset_notify_state fixture.
2026-04-22 18:07:52 -04:00
Justin Bollinger 9e0f040270 feat(notify): add test_pushover_notification helper
Canned send path so a user can verify Pushover credentials without
running an attack. Ignores the global notify_enabled toggle — the test's
purpose is to confirm the pipe is live, not that attack notifications
are enabled. Prints a note when the global toggle is OFF so the user is
not confused later.
2026-04-22 17:56:35 -04:00
Justin BollingerandClaude Opus 4.7 97bcc0ac78 test: isolate notify state between tests
hate_crack.main calls notify.init() at import time with whatever
config.json is resolved from the developer's environment (often
~/.hate_crack/config.json). If that file has notify_enabled: true, the
per-attack prompt in attacks.py fires input() during tests and trips
pytest's capture fd, failing unrelated tests.

Add an autouse conftest fixture that clears notify module state before
and after every test so the suite is hermetic regardless of local
config. Notify-specific tests already use their own
clear_state_for_tests() fixture; this change covers the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:27:15 -04:00
Justin BollingerandClaude Opus 4.7 6873156ef6 fix: propagate --username to hashcat --show potfile checks
`_run_hashcat_show` is called during the initial potfile check (main
preprocessing) and by `combine_ntlm_output` via `check_potfile()`. It
invokes `hashcat --show` via `subprocess.run` and previously did not go
through `_append_potfile_arg`, so it never received `--username`.

Without `--username`, hashcat treats the first colon of a `user:hash`
line as part of the hash and fails to match any potfile entries. This
caused the initial "already cracked" check to report zero hits for
legitimately cracked `user:hash` input files.

Route the command build through `_maybe_append_username_flag` before
invoking subprocess, mirroring the `_append_potfile_arg` pattern for
normal attack commands. Adds `TestUsernameInjectionIntoShow` covering
both flag-set and flag-unset code paths.

Refs #107

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:18:31 -04:00
Justin BollingerandClaude Opus 4.7 b5af2498f5 feat(notify): wire Pushover notifications into attacks and config (WIP)
Adds notify_* keys to both config.json.example files, threads
notification calls through hashcat invocations in main.py, and
exposes menu/attack hooks. Pushed for manual testing — verification
and PR still pending.

Refs #106

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:16:12 -04:00
Justin BollingerandClaude Sonnet 4.6 aae5ce8e90 feat(notify): add notification package with Pushover backend
Introduce hate_crack.notify package with a small functional public API
and a CrackTailer thread for polling hashcat output files. Package
layout keeps the HTTP call (_send_pushover) isolated so future backends
(Slack, generic webhooks) can be added as a sibling function rather
than a framework rewrite.

Core pieces:
- settings.py: NotifySettings dataclass plus atomic config persistence
  (save_enabled, add_to_allowlist) via read-modify-write + os.replace.
- pushover.py: single _send_pushover() that never raises; network
  errors, missing requests, and missing creds all funnel to False.
- _suppress.py: thread-local suppression context manager so
  orchestrator attacks can chain primitives without flooding
  notifications.
- tailer.py: CrackTailer(threading.Thread) that seeks to EOF on start,
  polls at a user-configurable interval, and collapses per-tick bursts
  into a single aggregate notification when they exceed the cap.
- __init__.py: public API (init, prompt_notify_for_attack,
  notify_job_done, notify_crack, start_tailer, stop_tailer,
  toggle_enabled, suppressed_notifications). Privacy guarantee:
  notification payloads contain only attack name, counts, and hash
  path, never plaintexts.

72 new tests cover dataclass defaults, atomic config writes, idempotent
allowlist updates, HTTP payload privacy, suppression nesting and
thread-locality, tailer EOF seek, burst cap, truncation recovery, and
the per-attack prompt's [y/n/always] flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:41:38 -04:00
Justin BollingerandClaude Sonnet 4.6 41ddf0e3e5 test: cover username detection and --username injection
Unit tests for `detect_username_hash_format`: per-mode positives
(MD5/SHA1/SHA256/SHA512/NTLM/LM), negatives (bare hash, wrong hex
length, non-hex, mixed valid/invalid, pwdump, trailing garbage),
blocklist modes, unknown modes, and file-handling (BOM, CRLF, null
bytes, unicode username, comments, blank lines, missing/empty file,
sample_size).

Integration tests for the injection flow: asserts `--username` appears
in the command emitted by `hcatBruteForce` when `hcatUsernamePrefix` is
set, asserts no duplicate when `hcatTuning` already includes
`--username`, and verifies `_append_potfile_arg` still injects the flag
even when called with `use_potfile_path=False`.

Refs #107

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:39:53 -04:00
Justin BollingerandClaude Sonnet 4.6 8228589c51 feat: wire --username auto-injection into hashcat commands
Calls `detect_username_hash_format` once during preprocessing in
`main()` (after NTLM/NetNTLM handling and before the potfile check) and
stores the result in `hcatUsernamePrefix`. A new helper
`_maybe_append_username_flag` is invoked from the universal
command-finalization chokepoint `_append_potfile_arg`, so every hashcat
command path automatically receives `--username` when a `user:hash`
file is detected. The helper guards against duplicates if `--username`
is already present in `hcatTuning`.

Skips detection for modes 1000/5500/5600 because the existing NTLM
preprocessing already strips usernames into a bare `.nt` file.

Also adds `hcatUsernamePrefix` to the proxy sync tuple in
`hate_crack.py` so tests that patch the root module propagate into
`hate_crack.main`.

Refs #107

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:39:45 -04:00
Justin BollingerandClaude Sonnet 4.6 9dcc2f2c4a feat: add username:hash format detection module
New pure-logic module `hate_crack.username_detect` exposes
`detect_username_hash_format`, which samples the first N non-empty lines
of a hash file and checks that every line matches `user:<hex>` with the
exact hex length expected for the given hashcat mode.

Ships allowlist of MD5/SHA1/SHA256/SHA512/MD4/NTLM/LM-family modes and
blocklist for WPA, IKE-PSK, NetNTLM, and non-hex formats.

Refs #107

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:39:37 -04:00