Commit Graph
610 Commits
Author SHA1 Message Date
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 BollingerandGitHub 4f1321cbe4 Merge pull request #109 from trustedsec/feat/pushover-notifications
feat: add Pushover notifications for hashcat jobs
v2.7.0
2026-04-22 20:42:45 -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 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 Opus 4.6 28d442a63e feat: add auto-tagging workflow for semver bumps
Automatically creates a new git tag on push to main based on
conventional commit prefixes: feat: bumps minor (2.5.x → 2.6.0),
fix:/perf: bumps patch (2.5.1 → 2.5.2). The new tag triggers the
existing release workflow to create a GitHub release.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v2.6.0
2026-04-13 10:32:40 -04:00
Justin BollingerandClaude Opus 4.6 622381f1b6 fix: use no-local-version scheme for clean semver display
Add local_scheme = "no-local-version" to setuptools-scm config so
versions never include the +g<hash> suffix. Simplify the regex in
__init__.py to only strip .post/.dev suffixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:29:55 -04:00
Justin BollingerandClaude Opus 4.6 1b5f6bee2e fix: correct convergence logic in hcatFingerprint loop
The old loop reassigned crackedBefore at the top of each iteration and
initialized crackedAfter to 0, which could cause the loop to enter
spuriously or skip entirely. Switch to while True / break to properly
detect when an iteration produces no new cracks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:10:01 -04:00
Justin BollingerandClaude Opus 4.6 2b169ae362 perf: combine d3ad0ne + T0XlC rules into single hashcat invocation
Merge both rule files into a temporary combined file so hashcat only
starts once per wordlist instead of twice, saving GPU initialization
overhead on each dictionary attack iteration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:09:55 -04:00
Justin BollingerandClaude Opus 4.6 c75572c8fc perf: use binary chunk counting in lineCount for large files
Read 1 MiB binary chunks and count newline bytes instead of iterating
text lines, which is significantly faster on large output files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:09:47 -04:00
Justin BollingerandClaude Opus 4.6 fa940f67c5 perf: replace hard 15s sleep with proper rate limiter for Hashmob downloads
Introduces a shared _RateLimiter class (1 req/2s) instead of per-function
locks with a 15-second sleep. Also tunes backoff from 256s to 30s with
30s penalty increments for faster retry on rate-limited responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:09:39 -04:00
Justin BollingerandGitHub 33e12eebc4 Merge pull request #105 from trustedsec/fix/output-path-symlinks
Fix output file path and remove symlink creation
v2.5.1
2026-04-08 13:11:49 -04:00
Justin BollingerandClaude Opus 4.6 c1a2767be3 test: add e2e test for output file path correctness
Verifies that a pwdump file at /tmp/test_hashes.ntds produces output
at /tmp/test_hashes.ntds.out using real hashcat. Confirms no files
leak into the project directory. Gated behind HATE_CRACK_RUN_E2E=1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:07:33 -04:00
Justin BollingerandClaude Opus 4.6 8e83bdeea3 fix: only report combined output file when it exists
cleanup() previously always printed 'Cracked passwords combined in
X.out' even when combine_ntlm_output() returned early (no cracked
hashes) or the hash type wasn't NTLM pwdump. Now checks file
existence first and falls back to pointing at the raw .out file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:05:08 -04:00
Justin Bollinger cce1b9d466 Merge branch 'remove-symlink-logic' 2026-04-08 13:02:49 -04:00
Justin BollingerandClaude Opus 4.6 9b97cd2a48 fix: remove symlink/copy from _ensure_hashfile_in_cwd
Output files now land next to the original hashfile. resolve_path()
already resolves relative paths against HATE_CRACK_ORIG_CWD, so
relocating the hashfile into CWD was unnecessary and created
confusing symlinks in the working directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:02:26 -04:00
Justin BollingerandClaude Opus 4.6 fa07d37a84 fix: write output files to user's CWD, not install directory
The bash shim uses `uv run --directory <install_dir>` which changes the
process CWD to the install directory. _ensure_hashfile_in_cwd() and the
Hashview download path used os.getcwd() to determine the target directory
for output files (.out, .nt, etc.), causing them to land in the install
directory instead of where the user ran the command.

Add orig_cwd() helper that reads HATE_CRACK_ORIG_CWD (set by the shim)
and use it in _ensure_hashfile_in_cwd(), the Hashview download path, and
the potfile fallback path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:43:51 -04:00
Justin Bollinger d0bfb015a6 docs: update README for v2.5.0 changes 2026-03-20 11:15:29 -04:00