-
-
Notifications
You must be signed in to change notification settings - Fork 133
REMOTE SHELL integration using tmux to interact without ssh creds #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Good news everyone! There's a new (and long overdue) new version of hackingBuddyGPT out! To summarize the big changes: - @Neverbolt did extensive work on the configuration and logging system: - Overwork of the configuration system - Added a visual and live web based log viewer, which can be started with `wintermute Viewer` - Updated the configuration system. The new configuration system now allows loading parameters from a .json file as well as choosing which logging backend should be used - @lloydchang with @pardaz-banu, @halifrieri, @toluwalopeoolagbegi and @tushcmd added support for dev containers - @jamfish added support for key-based SSH access (to the target system) - @Qsan1 added a new use-case, focusing on enabling linux priv-esc with small-language models, to quote: - Added an extended linux-privesc usecase. It is based on 'privesc', but extends it with multiple components that can be freely switch on or off: - Analyze: After each iteration the LLM is asked to analyze the output of that round. - Retrieval Augmented Generation (RAG): After each iteration the LLM is prompted and asked to generate a search query for a vector store. The search query is then used to retrieve relevant documents from the vector store and the information is included in the prompt for the Analyze component (Only works if Analyze is enabled). - Chain of thought (CoT): Instead of simply asking the LLM for the next command, we use CoT to generate the next action. - History Compression: Instead of including all commands and their respective output in the prompt, it removes all outputs except the most recent one. - Structure via Prompt: Include an initial set of command recommendations in `query_next_command` I thank all our contributors (and hopefully haven't forgotten too many). Enjoy!
bump dependencies and add Qsan1's documentation
Signed-off-by: Emmanuel Ferdman <[email protected]>
Display query in the URL on failed request
Create dependency-review.yml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces local shell integration using tmux to enable hackingBuddyGPT to interact with shells without requiring SSH credentials. The feature allows users to connect to local tmux sessions for testing and development scenarios.
Key changes:
- Added LocalShellConnection class for tmux-based shell interaction
- Implemented LocalShellCapability for command execution through tmux
- Updated LinuxPrivesc use case to support both SSH and local shell connections
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hackingBuddyGPT/utils/local_shell/local_shell.py | Implements complete tmux-based shell connection with command execution, output capture, and completion detection |
| src/hackingBuddyGPT/utils/local_shell/init.py | Package initialization for local shell utilities |
| src/hackingBuddyGPT/usecases/privesc/linux.py | Updates LinuxPrivesc to support both SSH and local shell connections |
| src/hackingBuddyGPT/capabilities/local_shell.py | Implements capability for local shell command execution |
| README.md | Documentation updates explaining the new local shell feature and usage examples |
| subprocess.run(['tmux', 'set-option', '-t', self.tmux_session, 'history-limit', '10000'], | ||
| capture_output=True) | ||
| raise RuntimeError(f"Error executing command: {e}") | ||
|
|
Copilot
AI
Aug 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The run_simple_fallback method is quite long (49 lines) and handles multiple responsibilities including setting tmux options, command execution, and output extraction. Consider breaking this into smaller, focused methods.
| self._set_tmux_history_limit(50000) | |
| clear_marker = self._send_clear_marker() | |
| self.send_command(command) | |
| self.wait_for_command_completion() | |
| end_marker = self._send_end_marker() | |
| output = self.capture_output(50000) | |
| result = self._extract_output_between_markers(output, clear_marker, end_marker, command) | |
| self._set_tmux_history_limit(10000) | |
| return result | |
| except Exception as e: | |
| self._set_tmux_history_limit(10000) | |
| raise RuntimeError(f"Error executing command: {e}") | |
| def _set_tmux_history_limit(self, limit): | |
| subprocess.run(['tmux', 'set-option', '-t', self.tmux_session, 'history-limit', str(limit)], | |
| capture_output=True) | |
| def _send_clear_marker(self): | |
| clear_marker = f"__CLEAR_{uuid.uuid4().hex[:8]}__" | |
| self.send_command('clear') | |
| time.sleep(0.3) | |
| self.send_command(f'echo "{clear_marker}"') | |
| time.sleep(0.3) | |
| return clear_marker | |
| def _send_end_marker(self): | |
| end_marker = f"__END_{uuid.uuid4().hex[:8]}__" | |
| self.send_command(f'echo "{end_marker}"') | |
| time.sleep(0.5) | |
| return end_marker | |
| def _extract_output_between_markers(self, output, clear_marker, end_marker, command): | |
| lines = output.splitlines() | |
| start_idx = -1 | |
| end_idx = -1 | |
| for i, line in enumerate(lines): | |
| if clear_marker in line: | |
| start_idx = i | |
| elif end_marker in line and start_idx != -1: | |
| end_idx = i | |
| break | |
| if start_idx != -1 and end_idx != -1: | |
| result_lines = lines[start_idx + 1:end_idx] | |
| if result_lines and command in result_lines[0]: | |
| result_lines = result_lines[1:] | |
| result = '\n'.join(result_lines).strip() | |
| else: | |
| result = self._extract_recent_output(output, command) | |
| return result |
| result = self._extract_between_markers(final_output, start_marker, end_marker, command) | ||
| return result | ||
|
|
||
| except Exception as e: |
Copilot
AI
Aug 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching all exceptions with 'Exception' is too broad and can mask unexpected errors. Consider catching more specific exceptions like subprocess.CalledProcessError or RuntimeError.
| except Exception as e: | |
| except (RuntimeError, subprocess.CalledProcessError) as e: |
| subprocess.run(['tmux', 'set-option', '-t', self.tmux_session, 'history-limit', '10000'], | ||
| capture_output=True) | ||
| raise RuntimeError(f"Error executing command: {e}") | ||
|
|
Copilot
AI
Aug 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching all exceptions with 'Exception' is too broad and can mask unexpected errors. Consider catching more specific exceptions like subprocess.CalledProcessError.
| return result | |
| except subprocess.SubprocessError as e: | |
| raise RuntimeError(f"Error executing command: {e}") | |
| finally: | |
| subprocess.run(['tmux', 'set-option', '-t', self.tmux_session, 'history-limit', '10000'], | |
| capture_output=True) |
| elif end_marker in line and start_idx != -1: | ||
| end_idx = i | ||
| break | ||
|
|
Copilot
AI
Aug 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The history-limit is set to 50000 and then restored to 10000 in the finally block, but there's no guarantee the original value was 10000. Consider capturing and restoring the original value instead.
| original_history_limit = None | |
| try: | |
| # Get the current history-limit value | |
| result = subprocess.run( | |
| ['tmux', 'show-options', '-g', 'history-limit'], | |
| capture_output=True, text=True | |
| ) | |
| if result.returncode == 0: | |
| # Output is like: 'history-limit 10000' | |
| match = re.search(r'history-limit\s+(\d+)', result.stdout) | |
| if match: | |
| original_history_limit = match.group(1) | |
| # Set history-limit to 50000 | |
| subprocess.run(['tmux', 'set-option', '-t', self.tmux_session, 'history-limit', '50000'], | |
| capture_output=True) | |
| clear_marker = f"__CLEAR_{uuid.uuid4().hex[:8]}__" | |
| self.send_command('clear') | |
| time.sleep(0.3) | |
| self.send_command(f'echo "{clear_marker}"') | |
| time.sleep(0.3) | |
| self.send_command(command) | |
| self.wait_for_command_completion() | |
| end_marker = f"__END_{uuid.uuid4().hex[:8]}__" | |
| self.send_command(f'echo "{end_marker}"') | |
| time.sleep(0.5) | |
| output = self.capture_output(50000) | |
| lines = output.splitlines() | |
| start_idx = -1 | |
| end_idx = -1 | |
| for i, line in enumerate(lines): | |
| if clear_marker in line: | |
| start_idx = i | |
| elif end_marker in line and start_idx != -1: | |
| end_idx = i | |
| break |
| last_output_hash = None | ||
| last_cursor_pos = None | ||
| stable_count = 0 | ||
| min_stable_time = 1.5 # Reduced for faster detection |
Copilot
AI
Aug 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic number 1.5 for min_stable_time should be made configurable or defined as a class constant to improve maintainability.
| ) | ||
|
|
||
| def __call__(self, cmd: str) -> Tuple[str, bool]: | ||
| out, _, _ = self.conn.run(cmd) # This is CORRECT - use the commented version |
Copilot
AI
Aug 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment '# This is CORRECT - use the commented version' is confusing and doesn't provide useful information. Consider removing it or making it more descriptive.
| out, _, _ = self.conn.run(cmd) # This is CORRECT - use the commented version | |
| out, _, _ = self.conn.run(cmd) |
The feature enables us to use the product with web shells or scenarios where we do not have SSH credentials.
python src/hackingBuddyGPT/cli/wintermute.py LinuxPrivesc --conn=ssh
python src/hackingBuddyGPT/cli/wintermute.py LinuxPrivesc --conn=local_shell --conn.tmux_session=<tmux_session_name>[
hackGptdemo.mp4