From 93960e2d58568108ff7a8d66a64add1444ee5394 Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Fri, 24 Jul 2026 12:06:12 -0400 Subject: [PATCH] test(llm): cover per-rule hashcat loop and hash:password stripping Co-Authored-By: Claude Opus 4.8 --- hate_crack/main.py | 2 ++ tests/test_hcat_ollama.py | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/hate_crack/main.py b/hate_crack/main.py index 231b442..68fd1ab 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -2076,6 +2076,8 @@ def hcatOllama(hcatHashType, hcatHashFile, mode, context_data): ollamaUrl, ollamaModel, ollamaNumCtx, mode, gen_context ) except ValueError as e: + # Defensive: mode is already validated above, but keep an explicit, + # non-misleading message if generate_candidates ever rejects its input. print(f"Error: {e}") return except Exception as e: diff --git a/tests/test_hcat_ollama.py b/tests/test_hcat_ollama.py index 2da4d65..a63c4d5 100644 --- a/tests/test_hcat_ollama.py +++ b/tests/test_hcat_ollama.py @@ -77,6 +77,53 @@ def test_wordlist_mode_reads_file_and_passes_sample(ollama_env): assert "letmein" in ctx_data["sample"] +def test_wordlist_mode_strips_hash_prefix(ollama_env): + # hash:password lines should contribute only the post-colon plaintext. + dump = ollama_env.tmp_path / "dump.txt" + dump.write_text("aad3b435:Winter2024\nnocolonline\n") + with ollama_globals(ollama_env.tmp_path), \ + mock.patch("hate_crack.main.llm.generate_candidates", + return_value=["x"]) as gen, \ + mock.patch("subprocess.Popen", return_value=_make_proc()): + hc_main.hcatOllama("0", ollama_env.hash_file, "wordlist", str(dump)) + sample = gen.call_args[0][4]["sample"] + assert "Winter2024" in sample + assert "aad3b435" not in sample + assert "nocolonline" in sample + + +def test_per_rule_runs_hashcat_with_rule_flag(ollama_env): + rules_dir = str(ollama_env.tmp_path / "rules") + os.makedirs(rules_dir, exist_ok=True) + rule_file = os.path.join(rules_dir, "test.rule") + open(rule_file, "w").close() + calls = [] + + def track_popen(cmd, **kwargs): + calls.append(list(cmd)) + return _make_proc() + + with mock.patch.object(hc_main, "ollamaUrl", OLLAMA_URL), \ + mock.patch.object(hc_main, "ollamaModel", MODEL), \ + mock.patch.object(hc_main, "ollamaNumCtx", 2048), \ + mock.patch.object(hc_main, "hcatBin", "/usr/bin/hashcat"), \ + mock.patch.object(hc_main, "hcatTuning", ""), \ + mock.patch.object(hc_main, "hcatPotfilePath", ""), \ + mock.patch.object(hc_main, "rulesDirectory", rules_dir), \ + mock.patch("hate_crack.main.generate_session_id", return_value="s"), \ + mock.patch("hate_crack.main.llm.generate_candidates", + return_value=["Password1"]), \ + mock.patch("subprocess.Popen", side_effect=track_popen): + hc_main.hcatOllama("1000", ollama_env.hash_file, "target", + {"company": "X", "industry": "Y", "location": "Z"}) + + # call 0 = plain wordlist run, call 1 = rule run + assert len(calls) == 2 + rule_call = calls[1] + assert "-r" in rule_call + assert rule_file in rule_call + + def test_missing_wordlist_prints_error(ollama_env, capsys): with ollama_globals(ollama_env.tmp_path), \ mock.patch("hate_crack.main.llm.generate_candidates") as gen: