Clearing before the join left a race in the normal path: a spinner thread
sitting just past its stop_event.is_set() check could repaint the line after
the erase, leaving the terminal dirty. Join first so no further writes are
possible, and nest the erase in a finally so an interrupted join (hate_crack
raises DoubleInterrupt on a second SIGINT) still leaves a clean line.
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove review-artifact "Defect 2" comment in main.py:2076; replace with
accurate description of the invalid-cap fallback. Same label removed from
the test section header in test_ollama_wordlist_sampling.py.
- Lift nested _usable helper to module-level _usable_plaintext in main.py,
placed alongside the other private wordlist helpers after _wordlist_path.
Add six direct unit tests covering blank, whitespace, plain, hash:pw,
multi-colon, and empty-plaintext cases.
- Move spinner line-clear before thread.join() in progress.py so the terminal
is cleaned up even if join() is interrupted by a DoubleInterrupt/second Ctrl-C.
Add test_tty_clears_line_even_if_join_raises to cover this path.
- Replace time.sleep-based elapsed-counter test with a deterministic version
that patches time.monotonic; assert on actual rendered format with r"\d+s".
Drop the misleading 0.05s sleep in test_tty_clears_line_on_exit (shorter
than one tick, so no frame was ever guaranteed).
- Add missing ollama* keys to hate_crack/config.json.example (package-data
copy); root config.json.example already had them.
Co-Authored-By: Claude <noreply@anthropic.com>
- Defect 1 (main.py): Replace floating-point stride loop with exact
floor(k * total / cap) index formula so sampling always returns
EXACTLY cap items for any 1 <= cap <= total_usable, including the
stride < 2 regime where the old approach returned cap-1 items.
- Defect 2 (main.py): Validate ollamaMaxSampleLines before computing
the stride; values <= 0 are nonsensical and fall back to 500
(same as the default) rather than raising ZeroDivisionError.
- Defect 3 (test_progress_spinner.py): Rewrite test_non_tty_no_extra_thread
to patch threading.Thread and assert it was never called, so the test
actually fails when the spinner starts a thread in the non-TTY path.
- Defect 4 (test_progress_spinner.py): Simplify test_non_tty_prints_message
to use monkeypatch.setattr instead of the dead mock.patch.object +
manual assign/restore layering; removes two # type: ignore comments.
- docs(README.md): Document ollamaMaxSampleLines alongside ollamaNumCtx /
ollamaTimeout in the Ollama Configuration section.
- tests: Add boundary-case tests for stride < 2 (total==cap, total==cap+1,
total=3/cap=2, cap=1) and zero-cap fallback; make
test_ollama_max_sample_lines_default exercise behaviour rather than
the ambient config value.
Co-Authored-By: Claude <noreply@anthropic.com>
Defect 1 — hcatOllama showed a plain print then blocked silently for
up to 300 s while waiting for the LLM. A new generic context manager
`hate_crack/progress.py::spinner()` now runs a daemon thread that
repaints a single line with a frame + elapsed-seconds counter every
~120 ms. TTY guard ensures non-TTY/test environments get a single
plain print instead of ANSI control characters. The line is erased
with \033[2K\r on exit (normal and exceptional).
Defect 2 — the wordlist path materialised every line in memory and
built a prompt that could massively overflow ollamaNumCtx on large
wordlists. The path now does a two-pass evenly-spaced sample: pass 1
counts usable lines, pass 2 stride-selects up to `ollamaMaxSampleLines`
(default 500, new config key) spread across the full file. When no
capping occurs the message reads "Loaded N passwords"; when capping
occurs it reads "Sampled N of M passwords".
Co-Authored-By: Claude <noreply@anthropic.com>
requires-python is ">=3.13", so a fresh worktree's "uv sync --dev" picks the
newest interpreter on the box - currently CPython 3.15.0a7 - and the build
fails because pyo3 0.26 (via jiter/fastuuid/pydantic-core) does not support
3.15 yet:
error: failed to run custom build command for `pyo3-ffi v0.26.0`
CI already pins 3.13 via setup-uv, so this only bit local worktrees. Pinning
matches CI and leaves requires-python alone.
Co-Authored-By: Claude <noreply@anthropic.com>
The Atomic Agents client was built with no timeout, so an Ollama server that
accepted the TCP connection but never replied (most commonly a large model still
loading into VRAM) left the CLI blocked in agent.run() forever. The caller's
`except Exception` handler only ever fired on connection-refused.
- llm.generate_candidates() takes a `timeout` parameter (default
DEFAULT_TIMEOUT_SECONDS = 300.0) and forwards it to the OpenAI client.
- openai.APITimeoutError is translated into a new domain-level
llm.LLMTimeoutError so main.py need not import openai itself, keeping the
atomic-agents/instructor dependency isolated to llm.py as documented.
- hcatOllama passes the new `ollamaTimeout` config value and prints
timeout-specific guidance (elapsed seconds, VRAM-loading hint, the setting to
raise) instead of the misleading "ensure Ollama is running" message.
- Documented `ollamaTimeout` in config.json.example and README.
Also closes two test gaps: the defensive `except ValueError` handler in
hcatOllama now has coverage, and the misleadingly-named `test_unknown_mode_raises`
is split into explicit unit tests of _build_request's mode validation. Ticked the
completed steps in the LLM Atomic Agents plan doc.
Co-Authored-By: Claude <noreply@anthropic.com>
hate_crack/llm.py imports all three directly, but only atomic-agents was
declared -- they were available purely as transitive deps. A future
atomic-agents release that loosened its coupling to any of them would make
`uv sync` silently omit it and break `import hate_crack.llm` at runtime.
Co-Authored-By: Claude <noreply@anthropic.com>
Conflicts resolved:
- CHANGELOG.md: kept both [2.12.0] (branch) and [2.11.4] (main), newest first
- pyproject.toml: kept main's tighter version pins (click>=8.4.2, requests>=2.34.2,
packaging>=26.2, pytest-cov==7.1.0) and the branch's atomic-agents>=2.0.0 dep
Co-Authored-By: Claude <noreply@anthropic.com>
The repo had no CI: ruff/ty/pytest ran only in local prek pre-push hooks, so
a commit pushed without hooks installed reached main unvalidated -- and
auto-tag would then cut a release from it. Adds ci.yml (ruff, ty, pytest on
3.13) and gates tagging on it.
Auto-tagged versions also never produced a release. The tag is pushed with
the default GITHUB_TOKEN, and GitHub suppresses workflow triggers for
GITHUB_TOKEN-created events, so the tag-triggered release.yml never fired --
v2.11.3 was tagged with no release. auto-tag now creates the release itself,
idempotently; release.yml stays as the manual-tag path.
Also fixes version logic that matched the `!` breaking marker but only ever
bumped minor (and never saw BREAKING CHANGE: footers, since only subjects
were inspected), serializes concurrent merges that both computed the same
tag, and repins softprops/action-gh-release from an arbitrary master commit
to v2.6.2 with all workflows on one actions/checkout version.
Clears the pre-existing ty error in notify/tailer.py that would have made
the new type-check step red: _read_new_lines read self._file_pos
(int | None) while its only None-guard lived in the caller, so the position
is passed in explicitly as an int. Behavior unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>