Skip to content

Commit 29939b2

Browse files
authored
Merge pull request #135 from ipa-lab/restructuring
Restructuring: split-up web-api-testing and web-api-documentation
2 parents b7f16bb + 825a795 commit 29939b2

31 files changed

+299
-955
lines changed

.github/copilot-instructions.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copilot Instructions for hackingBuddyGPT
2+
3+
## Project Summary
4+
5+
hackingBuddyGPT is a research-driven Python framework that helps security researchers and penetration testers use Large Language Models (LLMs) to automate and experiment with security testing, especially privilege escalation and web/API pentesting. It supports both local shell and SSH connections to targets, and is designed for rapid prototyping of new agent-based use cases. **Warning:** This tool executes real commands on live systems—use only in safe, isolated environments.
6+
7+
## Tech Stack
8+
- **Language:** Python 3.10+
9+
- **Core dependencies:** See `pyproject.toml` (notable: `fabric`, `requests`, `pydantic`, `pytest`)
10+
- **CLI Entrypoint:** `wintermute` (see `src/hackingBuddyGPT/cli/wintermute.py`)
11+
- **Web viewer:** Optional, for log viewing (`wintermute Viewer`)
12+
- **RAG/Knowledge base:** Markdown files in `rag/`
13+
- **Container/VM orchestration:** Bash scripts in `scripts/`, Ansible playbooks (`tasks.yaml`)
14+
15+
## Project Structure
16+
- `src/hackingBuddyGPT/` — Main Python package
17+
- `cli/` — CLI entrypoint (`wintermute.py`)
18+
- `capabilities/` — Modular agent actions (e.g., SSH, HTTP, note-taking)
19+
- `usecases/` — Agent logic for each use case (Linux privesc, web, API, etc.)
20+
- `utils/` — Shared helpers (LLM, logging, config, prompt generation)
21+
- `tests/` — Pytest-based unit and integration tests
22+
- `scripts/` — Setup, orchestration, and run scripts for Mac, Codespaces, and containers
23+
- `rag/` — Markdown knowledge base for RAG (GTFOBins, HackTricks)
24+
- `docs/` — Minimal, see https://docs.hackingbuddy.ai for full docs
25+
26+
## Setup & Usage
27+
- **Python:** Use 3.10+ (see `pyproject.toml`).
28+
- **Install:**
29+
```bash
30+
python -m venv venv
31+
source venv/bin/activate
32+
pip install -e .
33+
```
34+
- **Run:**
35+
- List use cases: `python src/hackingBuddyGPT/cli/wintermute.py`
36+
- Example: `python src/hackingBuddyGPT/cli/wintermute.py LinuxPrivesc --llm.api_key=... --conn=ssh ...`
37+
- See `README.md`, `MAC.md`, `CODESPACES.md` for platform-specific instructions.
38+
- **Testing:** `pip install '.[testing]' && pytest`
39+
- **Linting:** `ruff` (config in `pyproject.toml`)
40+
- **Container/VM setup:** Use scripts in `scripts/` (see comments in each script for prerequisites and usage).
41+
42+
## Coding Guidelines
43+
- Follow PEP8 and use `ruff` for linting (see `[tool.ruff]` in `pyproject.toml`).
44+
- Use type hints and docstrings for all public functions/classes.
45+
- Place new agent logic in `usecases/`, new capabilities in `capabilities/`.
46+
- Prefer composition (capabilities, helpers) over inheritance.
47+
- Use the logging utilities in `utils/logging.py`.
48+
- Document all new scripts and major changes in the `README.md` or relevant `.md` files.
49+
- Mark all workarounds or hacks with `HACK`, `TODO`, or `FIXME`.
50+
51+
## Existing Tools & Resources
52+
- **Documentation:** https://docs.hackingbuddy.ai
53+
- **Community/Support:** Discord link in `README.md`
54+
- **Security Policy:** See `SECURITY.md`
55+
- **Code of Conduct:** See `CODE_OF_CONDUCT.md`
56+
- **Contribution Guide:** See `CONTRIBUTING.md`
57+
- **Citations:** See `CITATION.cff`
58+
- **Benchmarks:** https://github.com/ipa-lab/benchmark-privesc-linux
59+
60+
## Tips to Minimize Bash/Build Failures
61+
- Always use the provided scripts for environment/container setup; do not run ad-hoc commands unless necessary.
62+
- Ensure Bash version 4+ (Mac: install via Homebrew).
63+
- Use virtual environments for Python dependencies.
64+
- For Codespaces/Mac, follow the step-by-step guides in `CODESPACES.md` and `MAC.md`.
65+
- Never expose the web viewer to the public internet.
66+
- Always set API keys and credentials in `.env` or as prompted by scripts.
67+
- For RAG, add new markdown files to the appropriate `rag/` subfolder.
68+
69+
---
70+
For further details, see the `README.md` and https://docs.hackingbuddy.ai. When in doubt, prefer existing patterns and scripts over inventing new ones.

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ scripts/mac_ansible_id_rsa
2626
scripts/mac_ansible_id_rsa.pub
2727
.aider*
2828

29-
src/hackingBuddyGPT/usecases/web_api_testing/documentation/openapi_spec/
3029
src/hackingBuddyGPT/usecases/web_api_testing/documentation/reports/
3130
src/hackingBuddyGPT/usecases/web_api_testing/retrieve_spotify_token.py
3231
config/my_configs/*
3332
config/configs/*
34-
config/configs/
33+
config/configs/
34+
35+
src/hackingBuddyGPT/usecases/web_api_documentation/openapi_spec/

src/hackingBuddyGPT/usecases/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .web import *
2+
from .web_api_documentation import *
23
from .web_api_testing import *
34
from .viewer import *
45
from .minimal_linux_privesc import *
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .simple_openapi_documentation import SimpleWebAPIDocumentation

src/hackingBuddyGPT/usecases/web_api_testing/utils/evaluator.py renamed to src/hackingBuddyGPT/usecases/web_api_documentation/evaluator.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import copy
2-
from itertools import chain
32

4-
from hackingBuddyGPT.usecases.web_api_testing.documentation.pattern_matcher import PatternMatcher
3+
from hackingBuddyGPT.utils.web_api.pattern_matcher import PatternMatcher
54

65

76
class Evaluator:
Lines changed: 144 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1+
import copy
2+
import json
13
import os
24
import re
35
from collections import defaultdict
46
from datetime import datetime
7+
from typing import Any, Dict, Optional, Tuple
58
import yaml
69
from hackingBuddyGPT.capabilities.yamlFile import YAMLFile
7-
from hackingBuddyGPT.usecases.web_api_testing.documentation.pattern_matcher import PatternMatcher
10+
from hackingBuddyGPT.utils.web_api.pattern_matcher import PatternMatcher
811
from hackingBuddyGPT.utils.prompt_generation.information import PromptStrategy
9-
from hackingBuddyGPT.usecases.web_api_testing.response_processing import ResponseHandler
10-
from hackingBuddyGPT.usecases.web_api_testing.utils import LLMHandler
12+
from hackingBuddyGPT.utils.web_api.llm_handler import LLMHandler
1113

1214

1315
class OpenAPISpecificationHandler(object):
1416
"""
1517
Handles the generation and updating of an OpenAPI specification document based on dynamic API responses.
1618
1719
Attributes:
18-
response_handler (object): An instance of the response handler for processing API responses.
1920
schemas (dict): A dictionary to store API schemas.
2021
filename (str): The filename for the OpenAPI specification file.
2122
openapi_spec (dict): The OpenAPI specification document structure.
@@ -26,18 +27,16 @@ class OpenAPISpecificationHandler(object):
2627
_capabilities (dict): A dictionary to store capabilities related to YAML file handling.
2728
"""
2829

29-
def __init__(self, llm_handler: LLMHandler, response_handler: ResponseHandler, strategy: PromptStrategy, url: str,
30+
def __init__(self, llm_handler: LLMHandler, strategy: PromptStrategy, url: str,
3031
description: str, name: str) -> None:
3132
"""
3233
Initializes the handler with a template OpenAPI specification.
3334
3435
Args:
3536
llm_handler (object): An instance of the LLM handler for interacting with the LLM.
36-
response_handler (object): An instance of the response handler for processing API responses.
3737
strategy (PromptStrategy): An instance of the PromptStrategy class.
3838
"""
3939
self.unsuccessful_methods = {}
40-
self.response_handler = response_handler
4140
self.schemas = {}
4241
self.query_params = {}
4342
self.endpoint_methods = {}
@@ -103,6 +102,143 @@ def is_partial_match(self, element, string_list):
103102

104103
return False
105104

105+
def parse_http_response_to_openapi_example(
106+
self, openapi_spec: Dict[str, Any], http_response: str, path: str, method: str
107+
) -> Tuple[Optional[Dict[str, Any]], Optional[str], Dict[str, Any]]:
108+
"""
109+
Parses an HTTP response to generate an OpenAPI example.
110+
111+
Args:
112+
openapi_spec (Dict[str, Any]): The OpenAPI specification to update.
113+
http_response (str): The HTTP response to parse.
114+
path (str): The API path.
115+
method (str): The HTTP method.
116+
117+
Returns:
118+
Tuple[Optional[Dict[str, Any]], Optional[str], Dict[str, Any]]: A tuple containing the entry dictionary, reference, and updated OpenAPI specification.
119+
"""
120+
121+
headers, body = http_response.split("\r\n\r\n", 1)
122+
try:
123+
body_dict = json.loads(body)
124+
except json.decoder.JSONDecodeError:
125+
return None, None, openapi_spec
126+
127+
reference, object_name, openapi_spec = self.parse_http_response_to_schema(openapi_spec, body_dict, path)
128+
entry_dict = {}
129+
old_body_dict = copy.deepcopy(body_dict)
130+
131+
if len(body_dict) == 1 and "data" not in body_dict:
132+
entry_dict["id"] = body_dict
133+
self.llm_handler._add_created_object(entry_dict, object_name)
134+
else:
135+
if "data" in body_dict:
136+
body_dict = body_dict["data"]
137+
if isinstance(body_dict, list) and len(body_dict) > 0:
138+
body_dict = body_dict[0]
139+
if isinstance(body_dict, list):
140+
for entry in body_dict:
141+
key = entry.get("title") or entry.get("name") or entry.get("id")
142+
entry_dict[key] = {"value": entry}
143+
self.llm_handler._add_created_object(entry_dict[key], object_name)
144+
if len(entry_dict) > 3:
145+
break
146+
147+
148+
if isinstance(body_dict, list) and len(body_dict) > 0:
149+
body_dict = body_dict[0]
150+
if isinstance(body_dict, list):
151+
152+
for entry in body_dict:
153+
key = entry.get("title") or entry.get("name") or entry.get("id")
154+
entry_dict[key] = entry
155+
self.llm_handler._add_created_object(entry_dict[key], object_name)
156+
if len(entry_dict) > 3:
157+
break
158+
else:
159+
if isinstance(body_dict, list) and len(body_dict) == 0:
160+
entry_dict = ""
161+
elif isinstance(body_dict, dict) and "data" in body_dict.keys():
162+
entry_dict = body_dict["data"]
163+
if isinstance(entry_dict, list) and len(entry_dict) > 0:
164+
entry_dict = entry_dict[0]
165+
else:
166+
entry_dict= body_dict
167+
self.llm_handler._add_created_object(entry_dict, object_name)
168+
if isinstance(old_body_dict, dict) and len(old_body_dict.keys()) > 0 and "data" in old_body_dict.keys() and isinstance(old_body_dict, dict) \
169+
and isinstance(entry_dict, dict):
170+
old_body_dict.pop("data")
171+
entry_dict = {**entry_dict, **old_body_dict}
172+
173+
174+
return entry_dict, reference, openapi_spec
175+
176+
def parse_http_response_to_schema(
177+
self, openapi_spec: Dict[str, Any], body_dict: Dict[str, Any], path: str
178+
) -> Tuple[str, str, Dict[str, Any]]:
179+
"""
180+
Parses an HTTP response body to generate an OpenAPI schema.
181+
182+
Args:
183+
openapi_spec (Dict[str, Any]): The OpenAPI specification to update.
184+
body_dict (Dict[str, Any]): The HTTP response body as a dictionary or list.
185+
path (str): The API path.
186+
187+
Returns:
188+
Tuple[str, str, Dict[str, Any]]: A tuple containing the reference, object name, and updated OpenAPI specification.
189+
"""
190+
if "/" not in path:
191+
return None, None, openapi_spec
192+
193+
object_name = path.split("/")[1].capitalize().rstrip("s")
194+
properties_dict = {}
195+
196+
# Handle different structures of `body_dict`
197+
if isinstance(body_dict, dict):
198+
for key, value in body_dict.items():
199+
# If it's a nested dictionary, extract keys recursively
200+
properties_dict = self.extract_keys(key, value, properties_dict)
201+
202+
elif isinstance(body_dict, list) and len(body_dict) > 0:
203+
first_item = body_dict[0]
204+
if isinstance(first_item, dict):
205+
for key, value in first_item.items():
206+
properties_dict = self.extract_keys(key, value, properties_dict)
207+
208+
# Create the schema object for this response
209+
object_dict = {"type": "object", "properties": properties_dict}
210+
211+
# Add the schema to OpenAPI spec if not already present
212+
if object_name not in openapi_spec["components"]["schemas"]:
213+
openapi_spec["components"]["schemas"][object_name] = object_dict
214+
215+
reference = f"#/components/schemas/{object_name}"
216+
return reference, object_name, openapi_spec
217+
218+
def extract_keys(self, key: str, value: Any, properties_dict: Dict[str, Any]) -> Dict[str, Any]:
219+
"""
220+
Extracts and formats the keys and values from a dictionary to generate OpenAPI properties.
221+
222+
Args:
223+
key (str): The key in the dictionary.
224+
value (Any): The value associated with the key.
225+
properties_dict (Dict[str, Any]): The dictionary to store the extracted properties.
226+
227+
Returns:
228+
Dict[str, Any]: The updated properties dictionary.
229+
"""
230+
if key == "id":
231+
properties_dict[key] = {
232+
"type": str(type(value).__name__),
233+
"format": "uuid",
234+
"example": str(value),
235+
}
236+
else:
237+
properties_dict[key] = {"type": str(type(value).__name__), "example": str(value)}
238+
239+
return properties_dict
240+
241+
106242
def update_openapi_spec(self, resp, result, prompt_engineer):
107243
"""
108244
Updates the OpenAPI specification based on the API response provided.
@@ -156,7 +292,7 @@ def update_openapi_spec(self, resp, result, prompt_engineer):
156292
return list(self.openapi_spec["endpoints"].keys())
157293

158294
# Parse the response into OpenAPI example and reference
159-
example, reference, self.openapi_spec = self.response_handler.parse_http_response_to_openapi_example(
295+
example, reference, self.openapi_spec = self.parse_http_response_to_openapi_example(
160296
self.openapi_spec, result, path, method
161297
)
162298

0 commit comments

Comments
 (0)