Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cce9f44
Fix permutation coverage and remove silent error swallowing
cursoragent Feb 20, 2026
e20c570
Merge pull request #1 from thumpersecure/cursor/code-quality-improvem…
thumpersecure Feb 20, 2026
42b00dc
Apply targeted enhancements to CLI, helpers, and updater
cursoragent Feb 20, 2026
9de4be3
Clean up Bandit nosec annotations in updater
cursoragent Feb 20, 2026
c83f1e9
Merge pull request #2 from thumpersecure/cursor/code-quality-improvem…
thumpersecure Feb 20, 2026
d8bf701
feat: Integrate PR #184 Patterns feature - replace permutations with …
cursoragent Feb 20, 2026
3eac32b
Merge pull request #3 from thumpersecure/cursor/pr-184-integration-9f55
thumpersecure Feb 20, 2026
4efdb3e
Fix permutation coverage and remove silent error swallowing
cursoragent Feb 20, 2026
5769e76
Apply targeted enhancements to CLI, helpers, and updater
cursoragent Feb 20, 2026
ebe811a
Clean up Bandit nosec annotations in updater
cursoragent Feb 20, 2026
d2f2f0d
Merge branch 'kaifcodec:main' into main
thumpersecure Feb 22, 2026
ba56761
Revert PR #184 changes and sync with upstream main
claude Feb 22, 2026
96caf39
Merge pull request #5 from thumpersecure/claude/sync-main-hM94Y
thumpersecure Feb 22, 2026
75c063c
Merge pull request #6
thumpersecure Feb 22, 2026
21d1fc2
0 (#7) (#8)
thumpersecure Feb 22, 2026
4e6ebe1
Fix permutation coverage and remove silent error swallowing
cursoragent Feb 20, 2026
3b33fe9
Apply targeted enhancements to CLI, helpers, and updater
cursoragent Feb 20, 2026
509fe69
Clean up Bandit nosec annotations in updater
cursoragent Feb 20, 2026
7c09f7b
Merge pull request #9 from thumpersecure/claude/fix-kaif-request-8wsej
thumpersecure Feb 22, 2026
d108b38
Merge pull request #10 from thumpersecure/main
thumpersecure Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
def test_generate_permutations():
perms = helpers.generate_permutations("user", "ab", limit=None)
assert "user" in perms
assert "userab" in perms
assert "userba" in perms
# All permutations must be valid
assert all(
p == "user" or
Expand All @@ -22,6 +24,7 @@ def test_generate_permutations():
def test_generate_permutations_email():
perms = helpers.generate_permutations("john@email.com", "abc", limit=None, is_email=True)
assert "john@email.com" in perms
assert "johnabc@email.com" in perms
assert all(
p == "john@email.com" or
(p.startswith("john") and len(p) > len("john@email.com") and p.endswith("@email.com"))
Expand Down Expand Up @@ -183,6 +186,14 @@ def test_bulk_usernames_skip_comments_blank_lines(tmp_path, run_main, capsys):
assert "Loaded 2 usernames" in out
assert exit_code == 0

def test_category_not_found_reports_category_name(run_main, capsys):
missing_category = "not_a_real_category"
exit_code = run_main(["-u", "someone", "-c", missing_category])
out = capsys.readouterr().out

assert f"category '{missing_category}' not found." in out
assert exit_code == 0

def test_username_file_unreadable(tmp_path, run_main):
username_file = tmp_path / "test_usernames.txt"
username_file.write_text("user")
Expand All @@ -208,7 +219,7 @@ def side_effect(*args, **kwargs):
proxy = kwargs.get("proxy")
instance = MagicMock()
if "invalid" in proxy:
instance.get.side_effect = Exception("connection failed")
instance.get.side_effect = helpers.httpx.HTTPError("connection failed")
else:
response = MagicMock()
response.status_code = 200
Expand Down
8 changes: 4 additions & 4 deletions user_scanner/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def main():
else:
print(
R +
f"[!] {'Email' if is_email else 'User'} category '{args.module}' not found." +
f"[!] {'Email' if is_email else 'User'} category '{args.category}' not found." +
Style.RESET_ALL
)
else:
Expand All @@ -301,8 +301,8 @@ def main():
old = json.load(f)
if isinstance(old, list) and all(isinstance(x, dict) for x in old):
data = old
except Exception:
pass
except (FileNotFoundError, json.JSONDecodeError, OSError):
data = []

new_items = json.loads(content)
if isinstance(new_items, list) and all(isinstance(x, dict) for x in new_items):
Expand All @@ -315,7 +315,7 @@ def main():
try:
with open(args.output, "r", encoding="utf-8") as init_file:
has_content = init_file.read().strip() != ""
except Exception:
except OSError:
has_content = False

with open(args.output, "a", encoding="utf-8") as f:
Expand Down
17 changes: 8 additions & 9 deletions user_scanner/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from types import ModuleType
from pathlib import Path
from typing import Dict, List, Optional
import random
import secrets
import threading
import httpx
from concurrent.futures import ThreadPoolExecutor, as_completed
Expand Down Expand Up @@ -86,8 +86,8 @@ def generate_permutations(username: str, pattern: str, limit: int | None = None,
if is_email:
username, domain = username.strip().split("@")

# generate permutations of length 1 len(chars)
for r in range(len(chars)):
# generate permutations of length 1 -> len(chars)
for r in range(1, len(chars) + 1):
for combo in permutations(chars, r):
new = username + ''.join(combo)
if is_email:
Expand All @@ -109,8 +109,8 @@ def test_proxy(proxy: str) -> Optional[str]:
response = client.get("https://www.google.com")
if response.status_code == 200:
return proxy
except Exception:
pass
except (httpx.HTTPError, OSError, ValueError):
return None
return None

with ThreadPoolExecutor(max_workers=max_workers) as executor:
Expand Down Expand Up @@ -166,7 +166,7 @@ def get_random_proxy(self) -> Optional[str]:
"""Get a random proxy from the list."""
if not self.proxies:
return None
return random.choice(self.proxies)
return secrets.choice(self.proxies)

def count(self) -> int:
"""Return the number of loaded proxies."""
Expand Down Expand Up @@ -202,7 +202,7 @@ def get_proxy_count() -> int:

# Function to return random user agent

def get_random_user_agent():
def get_random_user_agent() -> str:
agents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
Expand All @@ -214,6 +214,5 @@ def get_random_user_agent():
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36"
]
"""return random"""
random_agent = random.choice(agents)
return random_agent
return secrets.choice(agents)

8 changes: 4 additions & 4 deletions user_scanner/core/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ def load_local_version():
return "N/A", "file_missing"
except json.JSONDecodeError:
return "N/A", "json_error"
except Exception:
except OSError:
return "N/A", "error"


def get_pypi_version(pypi_url):
try:
pypi_version = httpx.get(pypi_url, timeout=7).json()["info"]["version"]
except Exception as e:
print(e)
response = httpx.get(pypi_url, timeout=7)
pypi_version = response.json()["info"]["version"]
except (httpx.HTTPError, ValueError, KeyError, TypeError):
return None
return pypi_version

Expand Down
6 changes: 4 additions & 2 deletions user_scanner/user_scan/gaming/monkeytype.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ def process(response):
# Surface Monkeytype validation errors (e.g. special characters)
try:
data = response.json()
except ValueError:
return Result.error("Invalid status code")

if isinstance(data, dict):
errors = data.get("validationErrors")
if errors:
return Result.error("; ".join(errors))
except Exception:
pass

return Result.error("Invalid status code")

Expand Down
21 changes: 14 additions & 7 deletions user_scanner/utils/update.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import subprocess
# Subprocess calls use fixed argument lists and shell=False.
import subprocess # nosec B404
import sys
from importlib.metadata import PackageNotFoundError, version

from colorama import Fore

def get_version(package_name):
def get_version(package_name: str) -> str:
try:
from importlib.metadata import version # Python 3.8+
return version(package_name)
except Exception:
except PackageNotFoundError:
return "Unknown"

def update_self():
print("Updating user-scanner using pip...\n")
try:
# Fixed command arguments; no user input is interpolated.
subprocess.check_call([
sys.executable, "-m", "pip", "uninstall", "user-scanner", "-y"
])
]) # nosec B603
# Fixed command arguments; no user input is interpolated.
subprocess.check_call([
sys.executable, "-m", "pip", "install", "user-scanner"
])
]) # nosec B603
except subprocess.CalledProcessError as e:
print(f"{Fore.RED}Failed to update user-scanner: {e}{Fore.reset}")
print(f"{Fore.RED}Failed to update user-scanner: {e}{Fore.RESET}")
return
except OSError as e:
print(f"{Fore.RED}Failed to run updater command: {e}{Fore.RESET}")
return


Expand Down