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>
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>
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>
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>
Replace input() with ctx.select_file_with_autocomplete() for all file
and directory path prompts in the 7 wordlist tools submenu functions.
Non-path prompts (lengths, masks, offsets, mode selection) remain as
plain input() calls.
Update tests to set ctx.select_file_with_autocomplete.side_effect for
file path values and leave builtins.input patches only for non-path
inputs.
- Delete block that wrongly appended found hashes back into the left
(unsolved) file - found hashes belong only in the potfile
- Fix get_hcat_potfile_path() to return "" when config key is
explicitly set to "", respecting user intent to disable potfile override
- Fix get_hcat_potfile_path() to resolve relative paths relative to the
config file directory, matching main.py's hate_path resolution
- Add potfile_path parameter to download_left_hashes() and
download_hashes_from_hashview() so CLI --potfile-path and
--no-potfile-path overrides propagate to the API merge step
- Update main.py call sites to pass hcatPotfilePath through
- Add tests covering all four bug fixes
When running as root or via sudo, /root/.local/bin may not be in PATH.
Use shutil.which with fallback to ~/.local/bin/uv, and fail clearly
if uv can't be found.
uv's PEP 517 build isolation copies source to a temp dir without .git,
so setuptools-scm can't determine the version and falls back to the
existing _version.py. Forcing --reinstall-package hate_crack makes uv
rebuild from source in the actual repo dir (editable mode) where git
is accessible, so the correct version is generated.
Refactors the upgrade logic into _run_upgrade() so it can be called
both from the startup version check prompt and directly via --update,
bypassing the version comparison entirely.
When a newer release is found, prompt the user to upgrade in-place.
Resolves the git repo root via `git rev-parse --show-toplevel` before
running `git pull && make clean && make && make install`, so the upgrade
works correctly whether hate_crack is run from source or installed into
site-packages. Ctrl-C and EOF at the prompt continue normally.
Resolves merge conflict with origin/main (keys 19-22 from main kept,
wordlist tools submenu added at key 80).
Fixes test isolation bug in test_random_rules_attack.py where
load_cli_module() nuked hate_crack.attacks from sys.modules, breaking
__globals__ references in wordlist_tools_submenu for downstream tests.
Also corrects wrong menu key assertion (21 not 20) for generate_rules_crack.
Resolves merge conflict with origin/main (keys 19-21 used by ngram,
permute, random-rules). Combipow takes key 22.
- gzip support: decompress .gz wordlists to a temp file before passing
path to combipow.bin (which requires a filename argument)
- UI line-counting uses gzip.open for .gz files
- Update tests to reference key 22 instead of 21
- Add _is_gzipped() magic-byte detector and _wordlist_path() context
manager that transparently decompresses gzip files to a temp path
- Apply gzip handling to hcatCombinator3 and hcatCombinatorX via
contextlib.ExitStack so compressed wordlists work without manual prep
- Add hcatNgramX() wrapper using ngramX.bin <corpus> <group_size> piped
to hashcat, with gzip auto-detection on the corpus file
- Add ngram_attack() handler in attacks.py with tab-autocomplete corpus
selection and configurable group size (default 3)
- Register attack as menu option 19 in both main.py and hate_crack.py
- Fix wordlist_optimizer.py: .app extension on macOS was wrong, use .bin
- Add tests/test_ngram_gzip.py covering ngram_attack handler, _is_gzipped,
and _wordlist_path context manager (temp file cleanup, plain passthrough)
- Merge combinator, combinator3, and combinatorX into one unified
combinator_crack function that routes by wordlist count:
2 (no sep) -> hcatCombination, 3 (no sep) -> hcatCombinator3,
4+ or any separator -> hcatCombinatorX
- Replace comma-separated wordlist input with one-at-a-time
tab-autocomplete prompts (blank line to finish)
- Add _prompt_wordlist_paths helper using existing readline infrastructure
- Add hcatCombinator3Wordlist and hcatCombinatorXWordlist config vars
with rockyou.txt defaults
- Print full hashcat command to stdout in --debug mode by calling
_debug_cmd at the end of _append_potfile_arg (covers all 27 invocations)
- Collapse combinator submenu from 6 options to 4; keep combinator3_crack,
combinatorX_crack, and combinator_3plus_crack as delegation shims
- Update tests to cover unified routing and new prompt interface
Replace direct module attribute mutation with patch.object for cleaner
test isolation. Use hate_crack.main.subprocess.Popen for targeted mocking
instead of the global subprocess.Popen.
Hashcat renames ~/.hashcat/sessions/hashcat.induct after each session.
When the directory is absent the loopback test fails with "No such file
or directory". Recreate the directory in the test setup so it always
exists when the loopback command runs.
Add menu option 21 (Combipow Passphrase Attack) using combipow.bin from
hashcat-utils. Generates all unique non-empty subset combinations from a
short wordlist and pipes them into hashcat stdin.
- hcatCombipow(hash_type, hash_file, wordlist, use_space_sep) in main.py
- combipow_crack handler in attacks.py validates file, enforces 63-line
limit, prompts for space separator, then delegates to hcatCombipow
- Wired into get_main_menu_items() and get_main_menu_options() in both
main.py and hate_crack.py
- 12 tests covering menu presence, handler delegation, line-count
enforcement, file validation, and subprocess flag construction
Extends the combinator submenu (option 6) with two new attacks using
hashcat-utils binaries that were already compiled but unused.
- hcatCombinator3: 3-way wordlist combination via combinator3.bin piped
to hashcat stdin
- hcatCombinatorX: 2-8 wordlist combination via combinatorX.bin with
optional --sepFill separator, piped to hashcat stdin
- combinator3_crack handler: prompts for 3 comma-separated wordlist paths
- combinatorX_crack handler: prompts for 2-8 paths plus optional separator
- combinator_submenu updated with options 5 and 6
Closes#84, closes#85
Adds Permutation Attack (menu option 19) that generates all character
permutations of each word in a targeted wordlist and pipes them to
hashcat via permute.bin from hashcat-utils.
- hcatPermute() in main.py: pipes permute.bin < wordlist | hashcat
- permute_crack() in attacks.py: prompts for single wordlist file with
factorial-growth warning, tab-autocomplete support
- Menu option 19 wired in both main.py and hate_crack.py
- hcatPermuteCount tracking alongside other count globals
- Tests: test_permute_attack.py (handler behavior) and
test_permute_wrapper.py (subprocess wiring)
- README: added entry in menu listing and attack descriptions
- Add hcatGenerateRules() in main.py: runs generate-rules.bin to
produce N random rules, writes them to a temp file, runs hashcat
with -r against a chosen wordlist, cleans up on exit
- Add generate_rules_crack() handler in attacks.py with count
prompt (default 65536), wordlist picker with tab-completion,
input validation, and abort on invalid input
- Add dispatcher generate_rules_crack() in main.py and key "20"
in both main.py and hate_crack.py get_main_menu_options()
- Add ("20", "Random Rules Attack") to get_main_menu_items()
- Add tests: test_random_rules_attack.py (menu presence, handler
wiring), test_random_rules_wrapper.py (subprocess behavior,
cleanup, count passing, count tracking)
- Add key "20" to MENU_OPTION_TEST_CASES in test_ui_menu_options.py
- Update README: add option 20 to menu listing, add attack
description, add version history entry
Adds Rule File Tools submenu (menu option 81) with three operations:
- Clean: removes invalid/duplicate rules via cleanup-rules.bin
- Optimize: consolidates redundant operations via rules_optimize.bin
- Clean and optimize: both in sequence with temp file handling
Wired through the standard three-layer pattern: main.py utility
functions + dispatcher, attacks.py handlers + submenu, root
hate_crack.py menu registration.
- Remove unnecessary gzip decompression logic
- hcstat2gen.bin can read both plain text and gzipped files directly
- Simplified code: just pass source file and output path as arguments
- Removed gzip and tempfile imports (no longer needed)
- Add comprehensive end-to-end tests covering all markov attack flows
- Tests verify training with plain text and gzipped wordlists
- Tests verify all handler menu paths (use existing, generate new, cancel, training failure)
- Fixes 'cannot uncompress data' error by not trying to decompress
- Update test cases to reflect combinator_submenu for key 6
- Remove test cases for keys 10/11/12 (moved to sub-menu)
- Add test cases for new keys 17 and 18
- Simplify adhoc_mask tests to avoid global state issues
- Add three hashcat wrapper functions: hcatAdHocMask, hcatMarkovTrain, hcatMarkovBruteForce
- Add corresponding attack handlers in attacks.py with OMEN-style training flow
- Consolidate 4 combinator attacks (keys 10/11/12) into interactive sub-menu (key 6)
- Add key 17 for ad-hoc mask attack and key 18 for markov brute force
- Update both main.py and hate_crack.py menu systems
- Add comprehensive test coverage for new handlers and wrappers
- Training source picker supports cracked passwords or any wordlist
Extract _select_rules() helper from quick_crack/loopback_attack and wire
it into omen_attack so OMEN can run with rule chains. Extend hcatOmen()
to accept and apply an hcatChains argument including debug mode injection.
Fix resolve_path() to honour HATE_CRACK_ORIG_CWD (set by the install shim)
so relative hash/wordlist paths resolve against the caller's working directory
instead of the repo root. Increase default omenMaxCandidates to 50M.
- Add _omen_model_is_valid() checking all 5 required model files
- Add _omen_model_info() reading model_info.json metadata
- hcatOmenTrain returns bool and writes training metadata
- Capture enumNG stderr and report errors instead of silent failure
- Rewrite omen_attack with train/use/cancel menu and wordlist picker
- Rewrite list_and_download_hashmob_rules with ThreadPoolExecutor
- Update quick_crack to use list_wordlist_files
- Add parallel download tests with stdin TTY mock
Config and assets were not found when running hate_crack from outside the
repo directory. The shim uses `uv run --directory` to always execute from
the repo root, and _candidate_roots() now includes _repo_root and
_package_path as fallback search locations.
A blank first line caused the format detection regex chain to fall
through to the error exit. Read lines in a loop and skip empty ones
before matching. Add tests for blank lines with LF, CRLF, whitespace,
and BOM variations.
Use utf-8-sig encoding to natively strip BOM and remove null bytes from
UTF-16 artifacts so the bare hash regex matches correctly. Replace the
unhelpful "unknown format" error with a diagnostic message showing the
actual first-line content and expected formats.
- Remove vendor-assets and clean-vendor Makefile targets
- Lima VM test and Dockerfile.test now use make install (editable)
- Remove hate_crack/hashcat-utils from submodules-pre expander generation
- Update README to reflect no-vendoring install
In dev checkouts where submodules are built in the repo root rather than
vendored into hate_path, OMEN binaries were not found. Introduces _omen_dir
that checks hate_path/omen first and falls back to the repo root omen dir.
Also removes vendor-assets from install/update targets and drops vendored
submodule paths from pyproject.toml package data.
When the Hashview server returns HTTP 200 with an error message and no
job_id (due to its internal notify_email bug), the CLI and interactive
paths now:
- exit 1 (not 0) in the CLI path
- print "✗ Error" instead of "✓ Success"
- print a hint to check the Hashview UI before retrying, preventing
duplicate job creation
Adds test for the error response path in test_cli_flags.py.
- Add rsync to _require_lima() prerequisite check; missing rsync now
skips cleanly instead of failing with an opaque command-not-found
- Add _truncate_output() helper and apply to all assertion messages to
keep failure output readable when make/install emits thousands of lines
- Increase limactl start timeout from 300s to 600s to accommodate slow
Ubuntu image downloads
- Add limactl stop before delete in cleanup for more reliable teardown
- Add flag verification to test_lima_vm_install_and_run: checks 10 CLI
flags in --help output, matching the local install test pattern
- Add 3 unit tests: test_truncate_output_trims_long_text,
test_truncate_output_short_text_unchanged,
test_require_lima_skips_without_rsync
OpenCL/device build errors are environment-specific issues, not code
bugs. Detect clCreateProgramWithBinary and kernel build failures in
stderr and pytest.skip instead of pytest.fail.