Commit Graph
100 Commits
Author SHA1 Message Date
Justin BollingerandClaude Opus 4.8 519d395708 docs(llm): document Atomic Agents refactor, new default model, wordlist mode (2.12.0)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 12:11:26 -04:00
Justin BollingerandClaude Opus 4.8 bcea02b2e7 test(llm): cover wordlist-mode abort when no wordlist picked
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 12:10:39 -04:00
Justin BollingerandClaude Sonnet 4.6 a4da571ab4 feat(llm): add wordlist (denylist) mode to LLM attack menu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 12:08:20 -04:00
Justin BollingerandClaude Opus 4.8 d6da721414 feat(llm): default Ollama model to qwen2.5:32b for structured output
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 12:06:29 -04:00
Justin BollingerandClaude Opus 4.8 93960e2d58 test(llm): cover per-rule hashcat loop and hash:password stripping
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 12:06:12 -04:00
Justin BollingerandClaude Sonnet 4.6 4df1121051 refactor(llm): delegate candidate generation to llm module; drop auto-pull
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 11:59:04 -04:00
Justin BollingerandClaude Opus 4.8 9dd6ad746f style(llm): drop noise Any annotation on agent result
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 11:52:32 -04:00
Justin BollingerandClaude Sonnet 4.6 a57a4165f1 refactor(llm): validate AgentConfig in prod; spec-mock the client in tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 11:50:19 -04:00
Justin BollingerandClaude Sonnet 4.6 1cc71abfef feat(llm): structured candidate generation module via Atomic Agents
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 11:48:19 -04:00
Justin Bollinger 66e6d593c4 build(deps): add atomic-agents for structured LLM candidate generation 2026-07-24 11:45:24 -04:00
Justin BollingerandClaude Opus 4.8 fe206420e9 fix(rules): pass required mode arg to cleanup-rules.bin (2.11.3)
cleanup-rules.bin requires a mode argument (1=CPU, 2=GPU) and exits with
usage text otherwise, so Rule File Tools cleanup always failed. Pass the
mode (defaulting to GPU) so cleanup actually runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 11:28:56 -04:00
Justin Bollinger 04be489c60 Merge branch 'fix/upload-counts' (2.11.2) 2026-07-23 14:26:31 -04:00
Justin BollingerandClaude Opus 4.8 a15860a63e feat(hashview): report cracked-hash upload counts (2.11.2)
The upload previously printed only "✓ Success: OK", so there was no way
to tell how many hashes were accepted. upload_cracked_hashes now surfaces
client-side uploaded/skipped counts in its return value, and the CLI
prints them plus the server's verified/updated/unmatched breakdown when a
newer Hashview reports them (see hashview #355/#356).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:26:26 -04:00
Justin Bollinger a14272a058 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	CHANGELOG.md
2026-07-23 14:00:58 -04:00
Justin Bollinger 4c70185270 Merge branch 'fix/upload-hex-decode' (2.10.11) 2026-07-23 13:59:22 -04:00
Justin BollingerandClaude Opus 4.8 a3ca2a19e8 docs(changelog): add 2.10.11 (Hashview $HEX upload fix + client-side validation)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:59:15 -04:00
Justin BollingerandClaude Opus 4.8 2ed9cc28e6 fix(hashview): decode $HEX[...] before upload for older servers
hate_crack forwarded hashcat's $HEX[...] plaintext verbatim. A Hashview
that verifies plaintext literally hashes the string "$HEX[..]" and
rejects the batch. Decode $HEX to the exact bytes the server must
re-hash and send those on the wire (body is now bytes):

- UTF-16LE modes (NTLM 1000, MSSQL 1731): latin-1 code points -> UTF-8
- raw-byte modes (0/100/300/900/1400/1700): the decoded bytes as-is
- unsafe (embedded CR/LF) or unknown modes: keep the $HEX token verbatim
  and rely on a $HEX-aware server

Also fixes the NTLM validation digest to use latin-1 -> UTF-16LE so
high-byte $HEX plaintexts verify instead of being silently unverifiable.
Verified end-to-end: the emitted wire verifies against both the old
(literal) and new ($HEX-aware) Hashview verifiers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:26:09 -04:00
Justin Bollinger d716f04c86 Merge branch 'fix/upload-hash-validation' 2026-07-23 13:12:41 -04:00
Justin BollingerandClaude Opus 4.8 ecedcbf7dc fix(hashview): validate cracked pairs client-side before upload
upload_cracked_hashes sent every hash:plaintext pair to Hashview
untouched. A single line whose plaintext doesn't match the declared
hash mode (e.g. a stray MD5 hash mixed into an NTLM list) made
Hashview reject the entire batch with an opaque
"Plaintext for hash ... was found to be invalid" error.

Add two client-side guards (default on, disable via validate=False):
1. Length filter — drop hashes whose hex width is wrong for the mode.
2. Plaintext verification — for reproducible fast modes (MD5, SHA1,
   MD4, NTLM, SHA2-256/512) recompute the digest from the plaintext
   ($HEX[...] decoded) and skip pairs that don't match.

Skipped lines are reported with line number and reason instead of
failing the whole upload; raise clearly if nothing valid remains.
Ships a pure-Python MD4 since OpenSSL 3 dropped it from hashlib.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:52:55 -04:00
Justin BollingerandClaude Opus 4.8 5510755c12 feat(wordlist): shard into all N parts in one run with numbered filenames
Shard Wordlist (option 7) now prompts for an output base path and a shard
count, then writes all N interleaved parts as base.001..base.00N in a single
pass instead of one file per modulus/offset invocation. Matches the intended
distributed-cracking workflow: split once, copy one part per node.

Updates tests and README usage docs; bumps CHANGELOG to 2.10.11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:51:07 -04:00
Justin Bollinger 1e23199aac Merge branch 'docs/changelog-version-correction' 2026-07-21 18:22:43 -04:00
Justin BollingerandClaude Opus 4.8 eb98103d05 docs: correct CHANGELOG versions (pytest fix is 2.10.10; add 2.10.9)
The auto-tag workflow already published v2.10.9 for Larry's Quick Crack
wordlist fix when main was pushed. Retitle the pytest security bump to
2.10.10 (its actual auto-tag target) and add the missing 2.10.9 entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 18:22:39 -04:00
Justin Bollinger 5dec58d973 Merge branch 'fix/bump-pytest-9.0.3-v2' 2026-07-21 18:21:01 -04:00
Justin BollingerandClaude Opus 4.8 c312259a86 fix(deps): bump pytest 9.0.2 -> 9.0.3 for GHSA-6w46-j5rx-g56g
The pinned dev/test dependency pytest==9.0.2 is affected by the
vulnerable tmpdir-handling advisory GHSA-6w46-j5rx-g56g (pytest < 9.0.3),
Dependabot alert #1. Bump the pin to 9.0.3 to clear it. Development
scope only (test runner); no runtime dependency change. Full test
suite passes under 9.0.3. (uv.lock is gitignored, so only the
pyproject.toml pin is tracked.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 18:20:55 -04:00
Justin Bollinger 6ed3b7577d Merge remote-tracking branch 'origin/main' 2026-07-21 18:18:15 -04:00
Justin Bollinger 1b888fa63f Merge branch 'docs/readme-2.10.8' 2026-07-21 18:16:41 -04:00
Justin BollingerandClaude Opus 4.8 8ee36d0eab docs: move version history from README into CHANGELOG.md
Consolidate the release notes into a single dedicated CHANGELOG.md
(Keep a Changelog format) and replace the README's inline Version
History with a pointer, so the two no longer drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 18:16:32 -04:00
Justin Bollinger 84d7fdd0ee Merge branch 'chore/changelog-2.10.8' 2026-07-21 18:13:07 -04:00
Justin BollingerandClaude Opus 4.8 b3fd8e1f90 docs: add CHANGELOG for v2.10.8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 18:13:03 -04:00
Justin Bollinger 755e43b91c Merge branch 'feat/hashview-rule-download' 2026-07-21 18:06:23 -04:00
Justin BollingerandClaude Opus 4.8 144d1a5334 feat(hashview): download rule files from /v1/rules
Add HashviewAPI.list_rules and download_rules wrapping the Hashview
rule endpoints. The server gzip-compresses plaintext rules on the
fly, so download_rules decompresses before saving, yielding a file
usable directly with hashcat -r. Wired into the interactive Hashview
menu (Download Rule) and the CLI (hashview download-rules
--rules-id/--output). Unknown rule ids surface the real HTTP 404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 18:06:19 -04:00
Justin Bollinger 201cc37e55 Merge branch 'fix/hashview-hashtype-parsing' 2026-07-21 17:56:34 -04:00
Justin BollingerandClaude Opus 4.8 61b197f10e fix(hashview): correct hash-type parsing for MD5 and hashfile listing
get_hashfile_details used 'or' fallthrough on hash_type, so MD5
(hash_type 0, falsy) resolved to the envelope 'type' string
('message') instead of 0. Select by key presence and drop the
bogus 'type' fallback.

get_hashfile_hash_type looked for file_ids/ids/hashfile_ids keys
that the endpoint never returns; it always yielded []. Read the
actual 'hashfiles' envelope array and extract each file id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:56:27 -04:00
Justin Bollinger df3554e077 Merge branch 'fix/hashview-native-json-decode' 2026-07-21 17:51:24 -04:00
Justin BollingerandClaude Opus 4.8 ace699b872 fix(hashview): accept native-JSON customer arrays from /v1/customers
The server now returns `users` as a native JSON array (issue #229,
no double-decode), but list_customers unconditionally ran
json.loads() on it, raising TypeError and breaking the entire
customer->hashfile enumeration flow. Accept both the native array
and the legacy double-encoded string, mirroring list_wordlists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:51:20 -04:00
Justin Bollinger 17a1d6794a Merge branch 'fix/updater-master-branch-repair' 2026-07-02 10:42:46 -04:00
Justin BollingerandClaude Opus 4.8 6c7b31dbb7 fix(update): repair master->main tracking in self-updater
Old clones made before the default-branch rename sit on a local `master`
whose upstream still points at the deleted refs/heads/master, so both a
bare `git pull` and `_run_upgrade()`'s `git checkout main` failed on stale
clones. The updater now fetches origin first, checks out main via
`git checkout -B main origin/main`, repairs the upstream to origin/main,
and pulls explicitly with `git pull origin main` so it never consults the
dangling branch.*.merge config. Existing dirty-branch and detached-HEAD
guards are unchanged.

Adds tests covering the renamed-clone migration and the fetch-failure bail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:42:42 -04:00
Justin Bollinger 00dcd74628 Merge branch 'fix/hashview-insufficient-data-skip' 2026-07-01 16:19:46 -04:00
Justin BollingerandClaude Opus 4.8 0c4f291174 test(hashview): skip job tests when server lacks cracked-hash history
The live upload-hashfile-job subprocess tests hard-failed when pointed at
a hash type the Hashview server has no cracked-hash history for: the
hashfile upload succeeds, but Hashview's task planner refuses to create a
job ("Not enough data to determine effective tasks for this hash type").
That is a server-side data limitation, not a client defect.

Treat that specific message as a skip (after asserting the hashfile
upload itself succeeded) across all three upload-job tests, so results
don't depend on which hash types have cracked data on the target
instance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:17:38 -04:00
Justin Bollinger 41b29fc55a Merge branch 'integrate/hashview-routes' 2026-06-25 11:53:10 -04:00
Justin BollingerandClaude Opus 4.8 1b31ecf705 docs: add v2.10.6 version-history entries for env override + local test harness
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:52:29 -04:00
Justin Bollinger 1af6984ff4 Merge branch 'feat/hashview-live-tests-local' into integrate/hashview-routes
# Conflicts:
#	hate_crack/api.py
#	tests/test_hashview.py
2026-06-25 10:55:49 -04:00
Justin BollingerandClaude Opus 4.8 048ed7cac1 feat(test): run live Hashview tests against a local docker stack
The live Hashview tests previously could only run against a remote server
configured in config.json, and the CLI ignored env-var overrides, so the
suite always hit prod. Prod runs an older Hashview whose /v1/jobs/add 500s
on a notify_email kwarg, which masked real client/server drift.

Wire the suite up to a local Hashview docker stack:

- main.py: HASHVIEW_URL / HASHVIEW_API_KEY env vars now override config.json,
  so the CLI can be pointed at a local instance without editing config.
- tests/_hashview_local.py + pytest_configure: when HASHVIEW_TEST_LOCAL=1,
  bring up + seed the Hashview compose stack (HASHVIEW_REPO), verify
  authenticated API access, export the HASHVIEW_* env, and tear down
  (HASHVIEW_KEEP=1 keeps it). Runs in configure so collection-time skipif
  on HASHVIEW_TEST_REAL sees the exported env.
- tests/hashview_local_seed.py: seed an admin api_key + non-default password
  (else Hashview's setup guard redirects every request to /setup), a
  customer, a hashfile, and cracked "effective task" data (else /v1/jobs/add
  has no tasks to schedule).
- api.py: download_left_hashes now uses GET /v1/hashfiles/<id>; the old
  /v1/hashfiles/<id>/left route 404s on current servers.
- subprocess tests: assert on the CLI's actual output ("Job ID:") rather than
  the literal "Job created", and strip ambient HASHVIEW_* creds from the
  no-key-configured check so the env override doesn't defeat it.
- README/CLAUDE: document HASHVIEW_TEST_LOCAL and the env overrides.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:17:49 -04:00
Justin BollingerandClaude Opus 4.8 c07bab1a2b fix(hashview): degrade to direct hashfile-ID entry when listing API absent
The hashfile-listing route (/v1/hashfiles/hash_type/<type>) only exists
on Hashview builds from 2026-06-08+ (v0.8.3-dev). On `main` and older
servers it 404s, which surfaced as "Could not list hashfiles" and a dead
end. The download flow is now version-adaptive:

- get_all_customer_hashfiles stops sweeping after the first 404 (the
  listing endpoint is absent) instead of probing every common type.
- The menu treats listing as best-effort: when no listing is available
  it prints a clear note and accepts a hashfile ID typed directly (read
  from the web UI), with no list-membership check. The chosen file's
  type is resolved via GET /v1/getHashType/<id> and downloaded via
  GET /v1/hashfiles/<id> -- both present on all server versions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:56:48 -04:00
Justin BollingerandClaude Opus 4.8 347e3529d5 feat(hashview): display a customer's hashfiles instead of asking for type
Downloading previously required knowing/typing the hash type because
Hashview only lists hashfiles per type. The download flow now sweeps
common hashcat modes (HashviewAPI.get_all_customer_hashfiles) and shows
all of the customer's uploaded hashfiles to pick from, deduped by id.
A manual hash-type prompt remains as a fallback only when nothing is
found among the common set (uncommon modes). The chosen file's real
hash type still drives the download.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:44:48 -04:00
Justin BollingerandClaude Opus 4.8 ab4700e636 fix(hashview): prompt for hash type when session type is unset
The download-hashes flow scoped the customer hashfile listing to
hcatHashType, but that is None when the Hashview menu is entered
without a hashfile loaded this session -> "No hashfiles of type None
found". Since Hashview only lists hashfiles per type, prompt for a
hashcat hash-type when none is set (Q cancels back to the menu) and
use it for the listing. Downstream selection still derives the real
type from the chosen hashfile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:40:36 -04:00
Justin BollingerandClaude Opus 4.8 90653b856f fix(hashview): correct remaining job/hashfile API routes
Continues the v0.8.3-dev API alignment beyond the hashfile-listing 404:

- download_left_hashes: "left" hashes are GET /v1/hashfiles/<id>
  (returns uncracked ciphertexts), not the nonexistent /<id>/left.
- delete_job: DELETE /v1/jobs/<id>, not GET /v1/jobs/delete/<id>.
- start_job: POST /v1/jobs/start/<id>, not GET (route is POST-only).
- stop_job: Hashview has no stop route; raise NotImplementedError
  pointing at delete_job rather than calling a phantom endpoint.
- found-hash bulk export: no such route exists (only single-hash
  POST /v1/search); the best-effort merge is documented and degrades
  gracefully on the expected 404.

Adds offline tests asserting start_job POSTs, delete_job uses DELETE,
and stop_job raises. Documents the release in README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:59:40 -04:00
Justin BollingerandClaude Opus 4.8 370764497e fix(hashview): list customer hashfiles via per-type endpoint
The customer->hashfile flow called GET /v1/hashfiles to list all
hashfiles, then filtered client-side. That route does not exist in
Hashview (confirmed against v0.8.3-dev), so it 404'd as soon as a
customer ID was entered ("Could not list hashfiles").

Hashview exposes no list-all route; the only enumeration endpoint is
GET /v1/hashfiles/hash_type/<hash_type>, which already returns
customer_id and hash_type per file. Rebuild get_customer_hashfiles on
top of get_hashfiles_by_type, scoped to the session hash type
(hcatHashType), and filter by customer. Also fix get_hashfile_details
to call GET /v1/getHashType/<id> instead of the nonexistent
/v1/hashfiles/<id>/hash_type. Remove the dead list_hashfiles wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:53:11 -04:00
Justin BollingerandClaude Opus 4.7 23ef425aef docs: add v2.10.5 version history entry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 12:02:17 -04:00
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