801 Commits
Author SHA1 Message Date
Justin BollingerandGitHub b672a07798 fix: raise omenMaxCandidates fallback default to 100,000,000 (#147)
The undocumented in-code default (1,000,000) let OMEN finish in ~2s
when config.json omitted the key, far below the 50,000,000 shipped in
config.json.example. Align both to 100,000,000 per issue #145.
v2.14.4
2026-07-28 17:16:59 -04:00
Justin BollingerandGitHub 8d33f1d528 Merge pull request #126 from trustedsec/dependabot/uv/ruff-0.16.0
chore(deps-dev): bump ruff from 0.15.1 to 0.16.0
2026-07-27 14:04:15 -04:00
Justin Bollinger 57c8666b6f Merge latest main into ruff bump
# Conflicts:
#	pyproject.toml
2026-07-27 14:02:36 -04:00
Justin BollingerandGitHub 452616f26c Merge pull request #131 from trustedsec/dependabot/uv/ty-0.0.63
chore(deps-dev): bump ty from 0.0.17 to 0.0.63
2026-07-27 14:01:07 -04:00
Justin BollingerandClaude Opus 4.7 fd28b3fcae build: pin ruff lint rule set to pre-0.16 defaults
ruff 0.16.0 broadened the default rule set and reports 317 findings
across the tree (65 auto-fixable) on code that 0.15.1 accepted
cleanly. Explicitly select the previous defaults ("E4", "E7", "E9",
"F") so this dep bump is behaviour-neutral; enabling the new rules
belongs in its own cleanup, not bundled into the version bump (per
the project's dep-bump convention).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-27 14:00:27 -04:00
Justin Bollinger 447849e2b3 Merge latest main into ruff bump for fix pass 2026-07-27 13:57:47 -04:00
Justin BollingerandClaude Opus 4.7 f3b6e04a3b build: unblock ty 0.0.63 dep bump
ty 0.0.63 changed two things that broke CI on this bump:
- new rules (unsupported-operator, no-matching-overload,
  invalid-assignment, unresolved-global) are error-severity by
  default and fire on the same legacy module-globals pattern in
  hate_crack/main.py the existing "warn" rules were pinned for;
  downgrade them to match.
- default exit behaviour now fails on warning-level diagnostics;
  pass --exit-zero-on-warning to ty check in CI and in the pre-push
  prek hook to restore the prior fail-only-on-errors semantics.

No source changes; deferring the main.py globals refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-27 13:57:06 -04:00
Justin Bollinger 3ac759fb84 Merge latest main into ty bump for fix pass 2026-07-27 13:50:30 -04:00
Justin BollingerandGitHub 3ba4a10861 Merge pull request #128 from trustedsec/dependabot/uv/beautifulsoup4-gte-4.15.0
chore(deps): update beautifulsoup4 requirement from >=4.12.0 to >=4.15.0
2026-07-27 13:49:58 -04:00
Justin BollingerandGitHub 735ff5a0e9 Merge pull request #127 from trustedsec/dependabot/uv/setuptools-gte-83.0.0
chore(deps-dev): update setuptools requirement from >=69 to >=83.0.0
2026-07-27 13:49:55 -04:00
Justin BollingerandGitHub 8e61e357d7 Merge pull request #115 from trustedsec/fix/pcfg-test-isolation
test: fix hate_path leak causing flaky test_main_pcfg
2026-07-27 13:49:52 -04:00
Justin BollingerandGitHub 7cb8cb5d88 Merge pull request #138 from trustedsec/docs/readme-menu-sync
docs: sync README with the current menu and undocumented features
2026-07-27 12:08:14 -04:00
Justin BollingerandClaude Fable 5 a73d48fe2f docs: sync README with the current menu and undocumented features
The main-menu block still showed the pre-consolidation numbering (LLM at 15,
OMEN at 16) and was missing Notifications, PCFG, and PRINCE-LING entirely.
Verified the block now matches get_main_menu_items() exactly.

Also documents features that shipped without README coverage:

- N-gram, PCFG, and PRINCE-LING attack sections
- pcfgRuleset / pcfgMaxCandidates / pcfgPrinceLingMaxCandidates config keys
- pcfg_cracker in the submodule and asset lists
- llm, menu, noninteractive, notify, username_detect, formatting, and
  progress modules in Project Structure
- LLM attack: structured output, target research pre-fill, spinner, timeout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:03:50 -04:00
Justin Bollinger d4b482ea99 Merge branch 'chore/secrets-scan-hook': detect-private-key gate (v2.14.3) v2.14.3 2026-07-25 17:41:18 -04:00
Justin BollingerandClaude Fable 5 d91dc0dc59 chore(ci): add detect-private-key pre-commit gate
The repo had no secret-scanning gate of any kind: prek.toml carried no
detect-private-key/detect-secrets hook, CI had none, and bandit only
scans hate_crack/ so it never inspected config files, docs, or fixtures
for committed key material.

Adds the detect-private-key hook from the pre-commit-hooks remote repo
at the pre-commit stage, and records v2.14.3 in the changelog alongside
the removal of the published agent tooling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 17:41:14 -04:00
Justin BollingerandClaude Fable 5 7e9c0a40fd chore: stop publishing local agent tooling and instructions
CLAUDE.md, the .claude/ directory, and the agent-generated planning docs
under docs/plans/ and docs/superpowers/ are local development aids, not
part of the shipped project. Untrack them and add them to .gitignore so
they stay local.

Drops the post-commit audit-docs hook from prek.toml, since the script it
invoked (.claude/audit-docs.sh) is no longer part of the repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 17:28:47 -04:00
Justin Bollinger 3d5755d8df Merge branch 'test/pipal-e2e': pipal parsing/shell fix + e2e tests, picker rename (v2.14.2) v2.14.2 2026-07-25 16:29:44 -04:00
Justin Bollinger deb99f17f8 docs: fold pipal fix into v2.14.2 changelog entry 2026-07-25 16:29:15 -04:00
Justin Bollinger 955e456301 Merge rename of _pick_training_wordlist into v2.14.2 2026-07-25 16:28:56 -04:00
Justin BollingerandClaude Opus 4.8 9d089d9a21 fix: pipal base-word parsing and shell-safe invocation
pipal() built one rigid regex requiring exactly pipal_count consecutive
base-word lines, so any cracked set with fewer unique base words than
pipal_count (default 10) matched nothing and returned []. Parse the
"Top N base words" section line by line instead, collecting up to
pipal_count words and stopping at the blank line that ends the section.

Also replace the shell=True string-formatted Popen with list-form
arguments so file paths containing shell metacharacters can't be
interpreted as commands.

Adds tests/test_pipal_e2e.py: hermetic end-to-end tests that drive the
real pipal() through a fake pipal executable (parsing, $HEX decode,
fewer-basewords regression, and injection safety).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 16:28:52 -04:00
Justin BollingerandClaude Opus 4.8 7a607df993 refactor: rename _omen_pick_training_wordlist to _pick_training_wordlist
The helper is shared by the OMEN attack and the LLM wordlist-mode attack,
so the OMEN-specific name was misleading.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 16:19:47 -04:00
Justin Bollinger ff70f7efd1 Merge branch 'fix/custom-path-tab-completion': tab completion on custom file-path prompts (v2.14.1) v2.14.1 2026-07-25 16:17:28 -04:00
Justin BollingerandClaude Opus 4.8 a7233c2f4f fix: tab completion on custom file-path prompts
The "p. Enter a custom path" branches of the OMEN and Markov training
pickers, the combipow wordlist prompt, and the rule cleanup/optimize
output-path prompts used a bare input() with no readline completer, so
TAB did nothing. Route them through select_file_with_autocomplete.

Also drop the path completer after each selection so later numeric-menu
and y/n prompts don't inherit stale file-path tab completion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 16:16:06 -04:00
dependabot[bot]andGitHub f2c0bc84e3 chore(deps-dev): bump ty from 0.0.17 to 0.0.63
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.17 to 0.0.63.
- [Release notes](https://github.com/astral-sh/ty/releases)
- [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ty/compare/0.0.17...0.0.63)

---
updated-dependencies:
- dependency-name: ty
  dependency-version: 0.0.63
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 19:56:29 +00:00
Justin BollingerandGitHub 761054250b Merge pull request #137 from trustedsec/chore/ci-mirror-hashview
ci: mirror hashview lint & security gates
2026-07-25 15:54:05 -04:00
Justin BollingerandClaude 4418c4e4d6 ci: mirror hashview lint/security gates
Adds the gates the hashview repo runs, adapted to hate_crack's uv toolchain:

- ruff format --check as a CI step + a ruff-format prek pre-push hook
  (the package was reformatted in the preceding commit).
- Bandit SAST vs a committed baseline (.bandit-baseline.json, 114 reviewed
  low-severity subprocess/shlex findings); [tool.bandit] config in
  pyproject.toml; CI job + prek pre-push hook. Only NEW findings fail.
- pip-audit dependency-CVE gate (CI job). PYSEC-2026-2447 (diskcache 5.6.3,
  transitive via instructor -> atomic-agents) is ignored — no upstream fix.
- Hygiene pre-commit hooks (trailing-whitespace, end-of-file-fixer, check-yaml,
  check-merge-conflict, check-added-large-files) via the pre-commit-hooks repo.
- CLAUDE.md updated: lint/security commands, prek install (+pre-commit hook
  type), and the active-hooks list.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-25 13:21:22 -04:00
Justin BollingerandClaude a13b573f79 style: apply ruff format to hate_crack
Normalizes formatting across the package so `ruff format --check` (added to
CI in a following commit) passes. No behavioral changes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-25 13:15:14 -04:00
Justin BollingerandGitHub 9db39be438 Merge pull request #136 from trustedsec/feature/cli-noninteractive
feat: non-interactive attack subcommands (#17)
v2.14.0
2026-07-24 20:37:34 -04:00
Justin BollingerandClaude dde6f92d96 test: stub hashcat potfile-recovery call in non-interactive integration tests
The pre-dispatch 'check POT file' step in main() shells out to the real
hashcat binary, which is absent in CI, causing the four test_main_* tests
to fail with FileNotFoundError. Stub _run_hashcat_show in the shared
_run_main helper — these tests verify dispatch routing and prompt
suppression, not potfile recovery.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 20:35:11 -04:00
Justin Bollinger d6af30c3db Merge remote-tracking branch 'origin/main' into feature/cli-noninteractive
# Conflicts:
#	CHANGELOG.md
2026-07-24 20:29:25 -04:00
Justin Bollinger 289da25609 docs: document non-interactive attack subcommands 2026-07-24 20:19:43 -04:00
Justin BollingerandClaude Sonnet 4.6 1a8bacfbc7 fix: resolve --rules dest collision, non-blocking dedup prompt, subcommand detection (#17)
- Change quick subparser --rules to dest=rule_files to prevent collision with the top-level --rules=store_true Hashmob download flag
- Update run_noninteractive to read args.rule_files instead of args.rules
- Move ATTACK_COMMANDS above run_noninteractive (Fix 5)
- Fix has_attack_subcommand to scan all argv elements (supports leading global flags like --debug)
- Replace raw input() dedup prompt with _auto_input() so non-interactive runs don't block
- Update existing quick dispatch tests to use rule_files= namespace key
- Add test_main_quick_with_rules_dispatches: proves --rules routes to crack, not download
- Add test_main_debug_flag_before_subcommand: proves leading global flags don't break routing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 20:15:47 -04:00
Justin BollingerandClaude Sonnet 4.6 e11a15d4c2 feat: wire non-interactive attack subcommands into the CLI (#17)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 20:07:48 -04:00
Justin BollingerandClaude Sonnet 4.6 1f0dcb4589 test: cover unknown-command and multi-rule dispatch paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 20:03:50 -04:00
Justin BollingerandClaude Sonnet 4.6 27791457fb feat: add non-interactive attack dispatcher
Appends `run_noninteractive(ctx, args)` to noninteractive.py, which
dispatches quick/dict/brute/topmask commands to the appropriate hcat*
function. Returns 0 on success, 1 on bad inputs, 2 on unknown command.
Includes 6 new unit tests (12 total in file), all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 20:01:06 -04:00
Justin BollingerandClaude Sonnet 4.6 db374edda2 refactor: harden rule-chain builder (types, empty-token guard, stronger tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 19:59:22 -04:00
Justin BollingerandClaude Sonnet 4.6 d2e45afb4a feat: add rule-chain builder for non-interactive mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 19:55:43 -04:00
Justin BollingerandGitHub 7a94009e49 Merge pull request #134 from trustedsec/fix/ollama-host-scheme
fix(llm): only prepend http:// to OLLAMA_HOST when no scheme is present
v2.13.1
2026-07-24 19:20:57 -04:00
Justin BollingerandClaude 2376ac5c10 fix(llm): only prepend http:// to OLLAMA_HOST when no scheme is present
ollamaUrl was built as "http://" + OLLAMA_HOST, so any value carrying a scheme
- the form Ollama's own tooling accepts - was mangled:

    OLLAMA_HOST=https://ollama.example.com
    -> http://https://ollama.example.com

The LLM attack then could not connect, and reaching a remote Ollama over TLS
was impossible. llm.py derives its client base URL as f"{ollamaUrl}/v1", so the
malformation propagated into the OpenAI-compatible client.

Extract _normalize_ollama_url() so the logic is unit-testable - ollamaUrl is
assigned at module import, which is otherwise only reachable via reload - and
prepend the scheme only when absent. Also strip trailing slashes, since callers
append paths. The bare host:port default is unchanged.

Fixes #119

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 19:19:38 -04:00
Justin BollingerandGitHub 9459c21b14 Merge pull request #133 from trustedsec/docs/changelog-2130
docs: split changelog to match the v2.12.0 and v2.13.0 tags
2026-07-24 19:11:52 -04:00
Justin BollingerandClaude e9727631be docs: split changelog to match the v2.12.0 and v2.13.0 tags
The UI polish, cracked-password mode, and target research were all written
into the [2.12.0] section on the assumption they would ship in that release.
They did not: auto-tag cuts a release per merge that contains feat/fix
commits, so #129 became v2.12.0 and #132 became v2.13.0.

Move those entries into a new [2.13.0] section so each heading lists what its
tag actually contains. No entry is duplicated or dropped.

Verified against 'git log v2.11.4..v2.12.0' and 'git log v2.12.0..v2.13.0'.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 19:10:47 -04:00
dependabot[bot]andGitHub 878a350c16 chore(deps): update beautifulsoup4 requirement from >=4.12.0 to >=4.15.0
Updates the requirements on [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/bs4/) to permit the latest version.

---
updated-dependencies:
- dependency-name: beautifulsoup4
  dependency-version: 4.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 23:03:55 +00:00
Justin BollingerandGitHub 748bf72e35 Merge pull request #132 from trustedsec/feat/ui-polish
feat(llm): cracked-password mode, target research, and LLM attack UI polish
v2.13.0
2026-07-24 19:03:20 -04:00
Justin BollingerandGitHub fe578777b1 Merge pull request #129 from trustedsec/feature/llm-atomic-agents
feat(llm): refactor LLM attack onto Atomic Agents, add wordlist mode (2.12.0)
v2.12.0
2026-07-24 19:01:44 -04:00
Justin BollingerandClaude 119dc29da4 docs: record UI polish and LLM mode additions under 2.12.0
2.12.0 is not yet tagged (latest tag is v2.11.4), so this work folds into that
entry rather than cutting a new version.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 18:53:39 -04:00
Justin BollingerandClaude a751342744 style(ui): normalize interactive prompt decoration and default-value format
- Remove [*] prefix from all input() prompts (10 sites); [*] is a
  status/output marker and must not appear in user-facing input prompts.
  print() calls that use [*] are untouched.
- Normalize default-value hints to the bare-parentheses form (7 instances
  already used it vs. 1 using "default N"); the lone outlier
  "\nEnter n-gram group size (default 3): " is rewritten to "(3)".
- Add \n prefix to combipow "Add spaces between words?" prompt so it does
  not run flush against the last output line of the wordlist path loop.
- All other prompts already satisfy: capital first letter, ends with ": ",
  and correct leading-whitespace context (tab-indented where the surrounding
  menu uses tabs, plain \n or none where it follows un-indented output).
- No input() calls added or removed (verified: attacks.py 42→42,
  main.py 26→26).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 18:51:48 -04:00
Justin BollingerandClaude 6448f550ee feat(llm): pre-fill LLM target industry/location from local model research
Target-info mode (menu 12) asked for company, industry, and location as three
blank prompts. Once the company name is known the local Ollama model can often
supply the other two, so ask it and offer the answers as editable defaults.

- llm.research_target(): TargetResearchInput/TargetResearchOutput schemas plus a
  research SystemPromptGenerator that tells the model to return empty strings
  when it does not genuinely recognize the organization, so an unknown small
  client yields blank prompts instead of a confident hallucination.
  APITimeoutError is translated to LLMTimeoutError like generate_candidates.
- clean_research_field(): strips, collapses whitespace, caps at 80 chars, and
  turns anything non-string into "" so model output cannot be pasted unbounded
  into a prompt default.
- main.hcatOllamaResearchTarget(): runs the call inside the existing spinner and
  degrades to blank suggestions on timeout or any other failure, so research can
  never block the attack.
- attacks.ollama_attack(): shows suggestions as "Industry (freight rail): "
  defaults, labelled explicitly as the model's GUESSES rather than OSINT.
- New ollamaAutoResearch config toggle (default true) in both config examples.

Research uses only the configured local Ollama server; the client name is never
sent to a search engine or third-party company-data API.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 18:42:50 -04:00
Justin BollingerandClaude 24e574bc8e fix(ui): print picker grids once and report rejected menu keys
The re-prompt loops repainted the whole multicolumn wordlist grid after every
typo, burying the error message under dozens of entries. Hoist the grid and the
p/q legend out of the loop so only the prompt repeats.

ollama_attack's loop also fell through to a silent redraw on an unrecognised
key, leaving no way to tell a rejected key from a repainted prompt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 18:33:11 -04:00
Justin BollingerandClaude 07ca6ba7e3 fix(ui): route ollama/omen submenus through interactive_menu; re-prompt pickers on invalid input
- ollama_attack: convert generation-mode menu to interactive_menu with
  dynamic items list (option 3 only present when cracked file exists);
  cancel via 99 or Escape; re-prompts in a while loop
- omen_attack: convert existing-model menu to interactive_menu; cancel
  via 99 or Escape (replaces hard-coded "3. Cancel"); re-prompts in loop
- _omen_pick_training_wordlist: replace abort-on-invalid with re-prompt
  loop; add explicit 'q' cancel key so users can exit without being
  trapped; callers already handle None correctly
- _markov_pick_training_source: same re-prompt-loop + 'q' cancel fix;
  callers already handle None correctly
- Update all affected tests to patch interactive_menu at
  hate_crack.attacks.interactive_menu (module-level import path) and
  drive follow-up prompts via builtins.input
- Add new tests: arrow-menu reach, Escape/99 cancel, re-prompt loop
  coverage for both pickers, and conditional option-3 key presence

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 18:31:04 -04:00
Justin BollingerandClaude 39e0dd956a feat(llm): add cracked-password generation mode to the LLM attack
Adds a third LLM Attack generation mode ("cracked") that samples the
plaintexts already recovered this session from <hashfile>.out and asks the
model to infer the target organization's password conventions and emit new
candidates in the same style.

- llm.py: new _CRACKED_PROMPT (offensive candidate generation, explicitly
  not the denylist-oriented _WORDLIST_PROMPT), a _PROMPTS mode->prompt map,
  and a "cracked" branch in _build_request that tells the model not to
  repeat already-cracked passwords.
- main.py: extract the wordlist sampling logic into the shared module-level
  helper _sample_plaintext_file(path, cap, source_label) and call it from
  both the wordlist and cracked branches of hcatOllama; guard missing and
  empty .out files.
- attacks.py: offer mode 3 only when <hashfile>.out exists and is non-empty
  (matching _markov_pick_training_source), with a clear message otherwise.
- README: document the three modes and the widened ollamaMaxSampleLines
  scope.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 18:18:52 -04:00