Conversation
- Merged upstream changes - Resolved conflicts in README.md, requirements.txt, bruteforce.py - Added Rich library integration - Applied hacker theme with green styling - Enhanced UI with panels, tables, and progress bars - Updated README with recent updates section
There was a problem hiding this comment.
Pull request overview
This PR updates the terminal UI to use the Rich library and introduces a CLI argument parsing flow intended to support automated/non-interactive runs.
Changes:
- Add
richas a dependency and rework terminal output (banner, prompts, tables, results) to use Rich styling. - Add argparse-based CLI flags and split the run flow into automated vs interactive modes.
- Update README to mention Rich UI enhancements and add a “Recent Updates” section.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| requirements.txt | Adds the rich dependency needed for the new terminal UI. |
| README.md | Updates feature bullet(s) and adds release notes, but currently diverges from the tool’s actual behavior. |
| bruteforce.py | Replaces ANSI UI with Rich, adds CLI parsing and refactors main flow; introduces a few functional regressions/bugs to address. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -13,67 +14,184 @@ | |||
| import re | |||
| import os | |||
| import secrets | |||
| import argparse | |||
| from bs4 import BeautifulSoup | |||
| from concurrent.futures import ThreadPoolExecutor, as_completed | |||
| from urllib.parse import urljoin, urlparse | |||
| from rich.console import Console | |||
| from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn | |||
| from rich.panel import Panel | |||
| from rich.text import Text | |||
| from rich.table import Table | |||
| from rich.live import Live | |||
| from rich.layout import Layout | |||
| from rich.syntax import Syntax | |||
| from rich.align import Align | |||
| from rich.style import Style | |||
| import sys | |||
| import os | |||
There was a problem hiding this comment.
bruteforce.py now imports sys and os twice, and several Rich-related imports (e.g., Progress, SpinnerColumn, Text, Live, Layout, Syntax, Align, Style) plus urljoin are unused in this file. This adds noise and can fail linting in environments that enforce unused-import checks; please remove duplicates and any unused imports (or start using them).
| WHITE = '\033[97m' | ||
| # Configure encoding for Windows compatibility | ||
| if sys.platform == 'win32': | ||
| os.environ['PYTHONIOENCODING'] = 'utf-8' |
There was a problem hiding this comment.
Setting PYTHONIOENCODING at runtime won’t change the encoding of already-initialized stdio streams (Python reads it at interpreter startup). If the intent is to fix Windows Unicode output, reconfigure sys.stdout/sys.stderr encoding (or avoid relying on this env var) so the workaround is actually effective.
| os.environ['PYTHONIOENCODING'] = 'utf-8' | |
| if hasattr(sys.stdout, "reconfigure"): | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| if hasattr(sys.stderr, "reconfigure"): | |
| sys.stderr.reconfigure(encoding='utf-8') |
| @@ -523,7 +410,7 @@ def crack_password_wrapper(password, cracker, state): | |||
|
|
|||
| # Update progress (overwrite same line) | |||
| total = state['total'] | |||
| sys.stdout.write(f"\r {C.BLUE}[*]{C.RESET} {progress_bar(current, total)} Current: {password[:20]:<20}") | |||
| sys.stdout.write(f"\r[*] {progress_bar(current, total)} {password[:20]:<20}") | |||
| sys.stdout.flush() | |||
There was a problem hiding this comment.
progress_bar() now returns Rich markup (e.g. [bright_cyan]...[/]), but the caller writes it via sys.stdout.write, so the markup will be printed literally instead of being styled. Either render the progress output via console.print(..., end="") / Rich Progress, or return plain-text characters when using sys.stdout.write.
| @@ -523,7 +410,7 @@ def crack_password_wrapper(password, cracker, state): | |||
|
|
|||
| # Update progress (overwrite same line) | |||
| total = state['total'] | |||
| sys.stdout.write(f"\r {C.BLUE}[*]{C.RESET} {progress_bar(current, total)} Current: {password[:20]:<20}") | |||
| sys.stdout.write(f"\r[*] {progress_bar(current, total)} {password[:20]:<20}") | |||
| sys.stdout.flush() | |||
There was a problem hiding this comment.
crack_password_wrapper updates progress output from worker threads without synchronizing stdout writes. With multiple threads this will interleave \r writes and produce garbled output (and potentially slow the attack due to heavy console I/O). Consider emitting progress only from the main thread (while iterating as_completed) or guard the stdout write with a lock shared across workers.
| parser.add_argument('--username-field', type=str, help='Override auto-detected username field name') | ||
| parser.add_argument('--password-field', type=str, help='Override auto-detected password field name') | ||
| parser.add_argument('--api-endpoint', type=str, help='Override auto-detected API endpoint (for JSON API mode)') | ||
| parser.add_argument('--login-mode', choices=['form', 'api'], help='Force login mode instead of auto-detecting') | ||
|
|
||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main(): | ||
| # Parse arguments FIRST before showing banner | ||
| args = parse_arguments() | ||
|
|
||
| # Show banner only for interactive mode or first automated call | ||
| banner() | ||
|
|
||
| section("STEP 1 — TARGET INFO") | ||
| print() | ||
|
|
||
| # Check if running in automated mode or interactive mode | ||
| if args.url and args.username and args.error: | ||
| # Automated mode - run with provided arguments (banner already shown) | ||
| run_attack_automated(args, show_banner=False) | ||
| else: | ||
| # Interactive mode - prompt user for input (banner already shown) | ||
| run_attack_interactive(show_banner=False) | ||
|
|
||
|
|
||
| def run_attack_automated(args, show_banner=True): | ||
| """Run attack in fully automated mode using command-line arguments.""" | ||
| if show_banner: | ||
| banner() | ||
|
|
||
| url = args.url | ||
| username = args.username | ||
| error_msg = args.error | ||
| max_workers = args.threads | ||
| password_file = args.passwords | ||
|
|
||
| # Validate URL | ||
| if not url.startswith('http'): | ||
| url = 'https://' + url | ||
|
|
||
| info(f"Starting automated attack on {url}") | ||
| info(f"Using {max_workers} concurrent threads") | ||
|
|
||
| # Auto-detect unless overridden (simplified for this version) | ||
| section("CONFIGURATION") | ||
| analysis = { | ||
| 'login_mode': 'form', | ||
| 'api_endpoint': url, | ||
| 'username_field': args.username_field or 'email', | ||
| 'password_field': args.password_field or 'password', | ||
| 'csrf_token': None, | ||
| 'csrf_value': None, | ||
| } |
There was a problem hiding this comment.
--api-endpoint is documented as an override, but it is never applied: run_attack_automated always sets analysis['api_endpoint'] to url and does not use args.api_endpoint. This makes the CLI flag ineffective and prevents JSON API mode from targeting the correct endpoint unless the login URL itself is the API endpoint.
| section("STEP 2 - REVIEW & ADJUST") | ||
|
|
||
| # ── Confirm / override ── | ||
| section("STEP 2 — REVIEW & ADJUST") | ||
| print() | ||
| dim("Press Enter to accept the auto-detected value, or type a new one.") | ||
| print() | ||
| analysis = { | ||
| 'login_mode': 'form', | ||
| 'api_endpoint': url, | ||
| 'username_field': 'email', | ||
| 'password_field': 'password', | ||
| 'csrf_token': None, | ||
| 'csrf_value': None, | ||
| } | ||
|
|
||
| analysis['username_field'] = prompt("Username field name", default=analysis['username_field'], | ||
| hint="The form field name for the username/email input") | ||
| analysis['username_field'] = prompt("Username field name", default=analysis['username_field']) | ||
| analysis['password_field'] = prompt("Password field name", default=analysis['password_field']) | ||
|
|
There was a problem hiding this comment.
Interactive mode no longer offers a way to select JSON API mode or provide an API endpoint (and it also no longer performs the README-described auto-detection). analysis is hard-coded to login_mode: 'form' and api_endpoint: url, which effectively disables JSON API support for interactive users.
| - JSON API login (modern SPA/React/Vue/Angular sites) | ||
| - Universal CSRF bypass (hidden inputs, meta tags, cookies, headers) | ||
| - Auto-detection of login type, field names, and API endpoints | ||
| - Multi-threaded with progress bar | ||
| - Color-coded terminal output | ||
| - Beautiful Rich-styled terminal output with hacker theme |
There was a problem hiding this comment.
The README still states the tool auto-detects login type/fields/API endpoint and shows prompts for API endpoint + login mode selection, but the updated bruteforce.py no longer performs auto-detection and interactive mode is hard-coded to login_mode='form' without asking for an API endpoint. Please update the README to match current behavior (or restore the removed auto-detection / mode selection).
| parser.add_argument('--threads', '-t', type=int, default=10, | ||
| help='Number of concurrent worker threads (default: 10)') | ||
|
|
||
| # Optional field customization | ||
| parser.add_argument('--username-field', type=str, help='Override auto-detected username field name') | ||
| parser.add_argument('--password-field', type=str, help='Override auto-detected password field name') | ||
| parser.add_argument('--api-endpoint', type=str, help='Override auto-detected API endpoint (for JSON API mode)') | ||
| parser.add_argument('--login-mode', choices=['form', 'api'], help='Force login mode instead of auto-detecting') | ||
|
|
||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main(): | ||
| # Parse arguments FIRST before showing banner | ||
| args = parse_arguments() | ||
|
|
||
| # Show banner only for interactive mode or first automated call | ||
| banner() | ||
|
|
||
| section("STEP 1 — TARGET INFO") | ||
| print() | ||
|
|
||
| # Check if running in automated mode or interactive mode | ||
| if args.url and args.username and args.error: | ||
| # Automated mode - run with provided arguments (banner already shown) | ||
| run_attack_automated(args, show_banner=False) | ||
| else: | ||
| # Interactive mode - prompt user for input (banner already shown) | ||
| run_attack_interactive(show_banner=False) | ||
|
|
||
|
|
||
| def run_attack_automated(args, show_banner=True): | ||
| """Run attack in fully automated mode using command-line arguments.""" | ||
| if show_banner: | ||
| banner() | ||
|
|
||
| url = args.url | ||
| username = args.username | ||
| error_msg = args.error | ||
| max_workers = args.threads | ||
| password_file = args.passwords |
There was a problem hiding this comment.
In automated/CLI mode, --threads is used directly as max_workers without validation. Passing 0 or a negative value will raise at ThreadPoolExecutor(max_workers=...). Please validate/coerce args.threads (similar to interactive mode) before starting the attack.
No description provided.