mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
fix: write output files to user's CWD, not install directory
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>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d0bfb015a6
commit
fa07d37a84
+6
-3
@@ -11,6 +11,7 @@ from typing import Callable, Optional, Tuple
|
||||
import requests # type: ignore[import-untyped]
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from hate_crack.cli import orig_cwd
|
||||
from hate_crack.formatting import print_multicolumn_list
|
||||
|
||||
_TORRENT_CLEANUP_REGISTERED = False
|
||||
@@ -841,8 +842,10 @@ class HashviewAPI:
|
||||
resp.raise_for_status()
|
||||
if output_file is None:
|
||||
output_file = f"left_{customer_id}_{hashfile_id}.txt"
|
||||
# Convert to absolute path to ensure file is preserved if CWD changes
|
||||
output_file = os.path.abspath(output_file)
|
||||
# Resolve relative paths against the user's original CWD, not the
|
||||
# install directory that ``uv run --directory`` may have switched to.
|
||||
if not os.path.isabs(output_file):
|
||||
output_file = os.path.join(orig_cwd(), output_file)
|
||||
output_abs = output_file
|
||||
total = int(resp.headers.get("content-length", 0))
|
||||
downloaded = 0
|
||||
@@ -869,7 +872,7 @@ class HashviewAPI:
|
||||
# Try to download found file and process with hashcat
|
||||
combined_count = 0
|
||||
combined_file = None
|
||||
out_dir = os.path.dirname(output_abs) or os.getcwd()
|
||||
out_dir = os.path.dirname(output_abs) or orig_cwd()
|
||||
found_file = os.path.join(out_dir, f"found_{customer_id}_{hashfile_id}.txt")
|
||||
|
||||
try:
|
||||
|
||||
@@ -4,6 +4,17 @@ import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def orig_cwd() -> str:
|
||||
"""Return the caller's original working directory.
|
||||
|
||||
When invoked via the bash shim (``uv run --directory``), the process CWD
|
||||
is the install directory, not where the user ran the command.
|
||||
``HATE_CRACK_ORIG_CWD`` preserves the real CWD so that output files
|
||||
(e.g. ``.out``, ``.nt``) are created next to the hashfile the user specified.
|
||||
"""
|
||||
return os.environ.get("HATE_CRACK_ORIG_CWD") or os.getcwd()
|
||||
|
||||
|
||||
def resolve_path(value: Optional[str]) -> Optional[str]:
|
||||
"""Expand user and return an absolute path, or None.
|
||||
|
||||
|
||||
+3
-5
@@ -69,6 +69,7 @@ from hate_crack.api import ( # noqa: E402
|
||||
extract_with_7z,
|
||||
)
|
||||
from hate_crack.cli import ( # noqa: E402
|
||||
orig_cwd,
|
||||
resolve_path,
|
||||
setup_logging,
|
||||
)
|
||||
@@ -198,10 +199,7 @@ def _ensure_hashfile_in_cwd(hashfile_path):
|
||||
"""Ensure hashfile path points to cwd to keep output files in execution dir."""
|
||||
if not hashfile_path:
|
||||
return hashfile_path
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except Exception:
|
||||
return hashfile_path
|
||||
cwd = orig_cwd()
|
||||
if not os.path.isabs(hashfile_path):
|
||||
return hashfile_path
|
||||
if os.path.dirname(hashfile_path) == cwd:
|
||||
@@ -358,7 +356,7 @@ if "hcatPotfilePath" not in config_parser:
|
||||
if os.path.isfile(_default_pot) or os.path.isdir(os.path.dirname(_default_pot)):
|
||||
hcatPotfilePath = _default_pot
|
||||
else:
|
||||
hcatPotfilePath = os.path.join(os.getcwd(), "hashcat.potfile")
|
||||
hcatPotfilePath = os.path.join(orig_cwd(), "hashcat.potfile")
|
||||
else:
|
||||
_raw_pot = (config_parser.get("hcatPotfilePath") or "").strip()
|
||||
if _raw_pot == "":
|
||||
|
||||
+14
-25
@@ -774,14 +774,8 @@ class TestHashviewAPI:
|
||||
content = f.read()
|
||||
assert content == b"hash1\nhash2\n"
|
||||
|
||||
def test_hashfile_orig_path_preservation(self, tmp_path):
|
||||
def test_hashfile_orig_path_preservation(self, tmp_path, monkeypatch):
|
||||
"""Test that original hashfile path is preserved before _ensure_hashfile_in_cwd"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
)
|
||||
from hate_crack.main import _ensure_hashfile_in_cwd
|
||||
|
||||
# Create a test hashfile in a different directory
|
||||
@@ -792,27 +786,22 @@ class TestHashviewAPI:
|
||||
|
||||
original_path = str(test_file)
|
||||
|
||||
# Save current directory
|
||||
orig_cwd = os.getcwd()
|
||||
try:
|
||||
# Change to tmp_path
|
||||
os.chdir(str(tmp_path))
|
||||
# Set HATE_CRACK_ORIG_CWD so _ensure_hashfile_in_cwd targets tmp_path
|
||||
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
|
||||
|
||||
# Call _ensure_hashfile_in_cwd
|
||||
result_path = _ensure_hashfile_in_cwd(original_path)
|
||||
# Call _ensure_hashfile_in_cwd
|
||||
result_path = _ensure_hashfile_in_cwd(original_path)
|
||||
|
||||
# The result should be different from original (in cwd now)
|
||||
# But original_path should still exist and be unchanged
|
||||
assert os.path.exists(original_path), "Original file should still exist"
|
||||
assert os.path.exists(result_path), "Result file should exist"
|
||||
# The result should be different from original (in cwd now)
|
||||
# But original_path should still exist and be unchanged
|
||||
assert os.path.exists(original_path), "Original file should still exist"
|
||||
assert os.path.exists(result_path), "Result file should exist"
|
||||
|
||||
# If they're different, result should be in cwd
|
||||
if result_path != original_path:
|
||||
assert os.path.dirname(result_path) == str(tmp_path), (
|
||||
"Result should be in cwd"
|
||||
)
|
||||
finally:
|
||||
os.chdir(orig_cwd)
|
||||
# If they're different, result should be in cwd
|
||||
if result_path != original_path:
|
||||
assert os.path.dirname(result_path) == str(tmp_path), (
|
||||
"Result should be in cwd"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -93,14 +93,14 @@ class TestEnsureHashfileInCwd:
|
||||
result = main_module._ensure_hashfile_in_cwd("relative/path.txt")
|
||||
assert result == "relative/path.txt"
|
||||
|
||||
def test_already_in_cwd(self, main_module, tmp_path):
|
||||
def test_already_in_cwd(self, main_module, tmp_path, monkeypatch):
|
||||
target = tmp_path / "hashfile.txt"
|
||||
target.write_text("hashes")
|
||||
with patch("os.getcwd", return_value=str(tmp_path)):
|
||||
result = main_module._ensure_hashfile_in_cwd(str(target))
|
||||
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
|
||||
result = main_module._ensure_hashfile_in_cwd(str(target))
|
||||
assert result == str(target)
|
||||
|
||||
def test_different_dir_existing_file_in_cwd(self, main_module, tmp_path):
|
||||
def test_different_dir_existing_file_in_cwd(self, main_module, tmp_path, monkeypatch):
|
||||
# File exists in a different directory
|
||||
other_dir = tmp_path / "other"
|
||||
other_dir.mkdir()
|
||||
@@ -113,11 +113,11 @@ class TestEnsureHashfileInCwd:
|
||||
cwd_copy = cwd_dir / "hashes.txt"
|
||||
cwd_copy.write_text("cwd version")
|
||||
|
||||
with patch("os.getcwd", return_value=str(cwd_dir)):
|
||||
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
||||
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(cwd_dir))
|
||||
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
||||
assert result == str(cwd_copy)
|
||||
|
||||
def test_different_dir_creates_symlink(self, main_module, tmp_path):
|
||||
def test_different_dir_creates_symlink(self, main_module, tmp_path, monkeypatch):
|
||||
# Source file in a different directory, nothing in cwd
|
||||
other_dir = tmp_path / "other"
|
||||
other_dir.mkdir()
|
||||
@@ -127,13 +127,34 @@ class TestEnsureHashfileInCwd:
|
||||
cwd_dir = tmp_path / "cwd"
|
||||
cwd_dir.mkdir()
|
||||
|
||||
with patch("os.getcwd", return_value=str(cwd_dir)):
|
||||
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
||||
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(cwd_dir))
|
||||
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
||||
|
||||
expected = str(cwd_dir / "hashes.txt")
|
||||
assert result == expected
|
||||
assert os.path.exists(expected)
|
||||
|
||||
def test_uses_orig_cwd_not_process_cwd(self, main_module, tmp_path, monkeypatch):
|
||||
"""Output files go to HATE_CRACK_ORIG_CWD, not the process CWD."""
|
||||
# Simulate: process CWD is the install dir, orig CWD is the user's dir
|
||||
install_dir = tmp_path / "install"
|
||||
install_dir.mkdir()
|
||||
user_dir = tmp_path / "user"
|
||||
user_dir.mkdir()
|
||||
other_dir = tmp_path / "other"
|
||||
other_dir.mkdir()
|
||||
source_file = other_dir / "hashes.txt"
|
||||
source_file.write_text("hashes")
|
||||
|
||||
monkeypatch.chdir(install_dir)
|
||||
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(user_dir))
|
||||
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
||||
|
||||
# Should land in user_dir, NOT install_dir
|
||||
assert result == str(user_dir / "hashes.txt")
|
||||
assert os.path.exists(user_dir / "hashes.txt")
|
||||
assert not os.path.exists(install_dir / "hashes.txt")
|
||||
|
||||
|
||||
class TestRunHashcatShow:
|
||||
def _make_mock_result(self, stdout_bytes):
|
||||
|
||||
@@ -8,6 +8,18 @@ from hate_crack import cli
|
||||
from hate_crack import formatting
|
||||
|
||||
|
||||
def test_orig_cwd_uses_env_var(monkeypatch, tmp_path):
|
||||
"""orig_cwd() returns HATE_CRACK_ORIG_CWD when set."""
|
||||
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
|
||||
assert cli.orig_cwd() == str(tmp_path)
|
||||
|
||||
|
||||
def test_orig_cwd_falls_back_to_getcwd(monkeypatch):
|
||||
"""orig_cwd() falls back to os.getcwd() when env var is unset."""
|
||||
monkeypatch.delenv("HATE_CRACK_ORIG_CWD", raising=False)
|
||||
assert cli.orig_cwd() == os.getcwd()
|
||||
|
||||
|
||||
def test_resolve_path_none_and_expand():
|
||||
assert cli.resolve_path("") is None
|
||||
resolved = cli.resolve_path("~")
|
||||
|
||||
Reference in New Issue
Block a user