Skip to content

Commit 83c92f5

Browse files
authored
Merge pull request #82 from ipa-lab/web_api_testing
Improved WebAPITesting ### New Features: - **ResponseAnalyzerWithLLM**: Introduced a new `ResponseAnalyzerWithLLM` to replace the previous `ResponseAnalyzer`, offering enhanced capabilities for large language model-based analysis. - **Task and State Planning**: Divided prompts into distinct tasks and state planning for improved processing. ### Improvements: - **Code Optimization**: Multiple optimizations were performed to improve code efficiency and performance. - **Refactoring**: Significant restructuring was done to improve code reusability and maintainability, particularly in the `web_api_testing` and `prompt_helper` components. - **Improved Documentation**: Expanded and clarified documentation throughout the codebase, including updates to `web_api_documentation` and additional data types. - **Enhanced Testing**: Added and adjusted tests for the new features and refactored components to ensure robust testing coverage. - **Pentesting Improvements**: Integrated a more sophisticated response analyzer for better pentesting capabilities and optimized token counting. ### Bug Fixes: - **Test Fixes**: Resolved issues with tests after code restructures and ensured all tests are now passing post-optimization. - **.gitignore Adjustments**: Updated `.gitignore` to exclude unnecessary files from version control.
2 parents 7120e68 + ff534cc commit 83c92f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2196
-595
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ dist/
1313
.coverage
1414
src/hackingBuddyGPT/usecases/web_api_testing/openapi_spec/
1515
src/hackingBuddyGPT/usecases/web_api_testing/converted_files/
16-
/src/hackingBuddyGPT/usecases/web_api_testing/utils/openapi_spec/
16+
/src/hackingBuddyGPT/usecases/web_api_testing/documentation/openapi_spec/
17+
/src/hackingBuddyGPT/usecases/web_api_testing/documentation/reports/

src/hackingBuddyGPT/cli/wintermute.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ def main():
88
parser = argparse.ArgumentParser()
99
subparser = parser.add_subparsers(required=True)
1010
for name, use_case in use_cases.items():
11-
subb = subparser.add_parser(
11+
use_case.build_parser(subparser.add_parser(
1212
name=use_case.name,
1313
help=use_case.description
14-
)
15-
use_case.build_parser(subb)
16-
x= sys.argv[1:]
17-
parsed = parser.parse_args(x)
14+
))
15+
16+
parsed = parser.parse_args(sys.argv[1:])
1817
instance = parsed.use_case(parsed)
1918
instance.init()
2019
instance.run()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .openapi_specification_handler import OpenAPISpecificationHandler
2+
from .report_handler import ReportHandler

src/hackingBuddyGPT/usecases/web_api_testing/utils/openapi_specification_manager.py renamed to src/hackingBuddyGPT/usecases/web_api_testing/documentation/openapi_specification_handler.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
import yaml
33
from datetime import datetime
44
from hackingBuddyGPT.capabilities.yamlFile import YAMLFile
5-
6-
class OpenAPISpecificationManager:
5+
from collections import defaultdict
6+
import pydantic_core
7+
from rich.panel import Panel
8+
9+
from hackingBuddyGPT.usecases.web_api_testing.response_processing import ResponseHandler
10+
from hackingBuddyGPT.usecases.web_api_testing.utils import LLMHandler
11+
from hackingBuddyGPT.utils import tool_message
12+
class OpenAPISpecificationHandler(object):
713
"""
814
Handles the generation and updating of an OpenAPI specification document based on dynamic API responses.
915
@@ -19,7 +25,7 @@ class OpenAPISpecificationManager:
1925
_capabilities (dict): A dictionary to store capabilities related to YAML file handling.
2026
"""
2127

22-
def __init__(self, llm_handler, response_handler):
28+
def __init__(self, llm_handler: LLMHandler, response_handler: ResponseHandler):
2329
"""
2430
Initializes the handler with a template OpenAPI specification.
2531
@@ -43,7 +49,6 @@ def __init__(self, llm_handler, response_handler):
4349
"components": {"schemas": {}}
4450
}
4551
self.llm_handler = llm_handler
46-
#self.api_key = llm_handler.llm.api_key
4752
current_path = os.path.dirname(os.path.abspath(__file__))
4853
self.file_path = os.path.join(current_path, "openapi_spec")
4954
self.file = os.path.join(self.file_path, self.filename)
@@ -149,6 +154,49 @@ def check_openapi_spec(self, note):
149154
note (object): The note object containing the description of the API.
150155
"""
151156
description = self.response_handler.extract_description(note)
152-
from hackingBuddyGPT.usecases.web_api_testing.utils.yaml_assistant import YamlFileAssistant
157+
from hackingBuddyGPT.usecases.web_api_testing.utils.documentation.parsing.yaml_assistant import YamlFileAssistant
153158
yaml_file_assistant = YamlFileAssistant(self.file_path, self.llm_handler)
154159
yaml_file_assistant.run(description)
160+
161+
162+
def _update_documentation(self, response, result, prompt_engineer):
163+
prompt_engineer.prompt_helper.found_endpoints = self.update_openapi_spec(response,
164+
result)
165+
self.write_openapi_to_yaml()
166+
prompt_engineer.prompt_helper.schemas = self.schemas
167+
168+
http_methods_dict = defaultdict(list)
169+
for endpoint, methods in self.endpoint_methods.items():
170+
for method in methods:
171+
http_methods_dict[method].append(endpoint)
172+
173+
prompt_engineer.prompt_helper.endpoint_found_methods = http_methods_dict
174+
prompt_engineer.prompt_helper.endpoint_methods = self.endpoint_methods
175+
return prompt_engineer
176+
177+
def document_response(self, completion, response, log, prompt_history, prompt_engineer):
178+
message = completion.choices[0].message
179+
tool_call_id = message.tool_calls[0].id
180+
command = pydantic_core.to_json(response).decode()
181+
182+
log.console.print(Panel(command, title="assistant"))
183+
prompt_history.append(message)
184+
185+
with log.console.status("[bold green]Executing that command..."):
186+
result = response.execute()
187+
log.console.print(Panel(result[:30], title="tool"))
188+
result_str = self.response_handler.parse_http_status_line(result)
189+
prompt_history.append(tool_message(result_str, tool_call_id))
190+
191+
invalid_flags = {"recorded", "Not a valid HTTP method", "404", "Client Error: Not Found"}
192+
if not result_str in invalid_flags or any(flag in result_str for flag in invalid_flags):
193+
prompt_engineer = self._update_documentation(response, result, prompt_engineer)
194+
195+
return log, prompt_history, prompt_engineer
196+
197+
def found_all_endpoints(self):
198+
if len(self.endpoint_methods.items())< 10:
199+
return False
200+
else:
201+
return True
202+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .openapi_converter import OpenAPISpecificationConverter
2+
from .openapi_parser import OpenAPISpecificationParser
3+
from .yaml_assistant import YamlFileAssistant
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,81 @@
11
import yaml
2+
from typing import Dict, List, Union
23

34
class OpenAPISpecificationParser:
45
"""
56
OpenAPISpecificationParser is a class for parsing and extracting information from an OpenAPI specification file.
67
78
Attributes:
89
filepath (str): The path to the OpenAPI specification YAML file.
9-
api_data (dict): The parsed data from the YAML file.
10+
api_data (Dict[str, Union[Dict, List]]): The parsed data from the YAML file.
1011
"""
1112

12-
def __init__(self, filepath):
13+
def __init__(self, filepath: str):
1314
"""
1415
Initializes the OpenAPISpecificationParser with the specified file path.
1516
1617
Args:
1718
filepath (str): The path to the OpenAPI specification YAML file.
1819
"""
19-
self.filepath = filepath
20-
self.api_data = self.load_yaml()
20+
self.filepath: str = filepath
21+
self.api_data: Dict[str, Union[Dict, List]] = self.load_yaml()
2122

22-
def load_yaml(self):
23+
def load_yaml(self) -> Dict[str, Union[Dict, List]]:
2324
"""
2425
Loads YAML data from the specified file.
2526
2627
Returns:
27-
dict: The parsed data from the YAML file.
28+
Dict[str, Union[Dict, List]]: The parsed data from the YAML file.
2829
"""
2930
with open(self.filepath, 'r') as file:
3031
return yaml.safe_load(file)
3132

32-
def get_servers(self):
33+
def _get_servers(self) -> List[str]:
3334
"""
3435
Retrieves the list of server URLs from the OpenAPI specification.
3536
3637
Returns:
37-
list: A list of server URLs.
38+
List[str]: A list of server URLs.
3839
"""
3940
return [server['url'] for server in self.api_data.get('servers', [])]
4041

41-
def get_paths(self):
42+
def get_paths(self) -> Dict[str, Dict[str, Dict]]:
4243
"""
4344
Retrieves all API paths and their methods from the OpenAPI specification.
4445
4546
Returns:
46-
dict: A dictionary with API paths as keys and methods as values.
47+
Dict[str, Dict[str, Dict]]: A dictionary with API paths as keys and methods as values.
4748
"""
48-
paths_info = {}
49-
paths = self.api_data.get('paths', {})
49+
paths_info: Dict[str, Dict[str, Dict]] = {}
50+
paths: Dict[str, Dict[str, Dict]] = self.api_data.get('paths', {})
5051
for path, methods in paths.items():
5152
paths_info[path] = {method: details for method, details in methods.items()}
5253
return paths_info
5354

54-
def get_operations(self, path):
55+
def _get_operations(self, path: str) -> Dict[str, Dict]:
5556
"""
5657
Retrieves operations for a specific path from the OpenAPI specification.
5758
5859
Args:
5960
path (str): The API path to retrieve operations for.
6061
6162
Returns:
62-
dict: A dictionary with methods as keys and operation details as values.
63+
Dict[str, Dict]: A dictionary with methods as keys and operation details as values.
6364
"""
6465
return self.api_data['paths'].get(path, {})
6566

66-
def print_api_details(self):
67+
def _print_api_details(self) -> None:
6768
"""
6869
Prints details of the API extracted from the OpenAPI document, including title, version, servers,
6970
paths, and operations.
7071
"""
7172
print("API Title:", self.api_data['info']['title'])
7273
print("API Version:", self.api_data['info']['version'])
73-
print("Servers:", self.get_servers())
74+
print("Servers:", self._get_servers())
7475
print("\nAvailable Paths and Operations:")
7576
for path, operations in self.get_paths().items():
7677
print(f"\nPath: {path}")
7778
for operation, details in operations.items():
7879
print(f" Operation: {operation.upper()}")
7980
print(f" Summary: {details.get('summary')}")
8081
print(f" Description: {details['responses']['200']['description']}")
81-
82-
# Usage example
83-
if __name__ == '__main__':
84-
openapi_parser = OpenAPISpecificationParser(
85-
'/hackingBuddyGPT/usecases/web_api_testing/openapi_spec/openapi_spec_2024-06-13_17-16-25.yaml'
86-
)
87-
openapi_parser.print_api_details()
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from openai import OpenAI
2+
from typing import Any
3+
4+
5+
class YamlFileAssistant:
6+
"""
7+
YamlFileAssistant is a class designed to interact with a YAML file using OpenAI's API.
8+
9+
Attributes:
10+
yaml_file (str): The path to the YAML file that the assistant will analyze.
11+
client (OpenAI): The OpenAI client used to interact with the OpenAI API.
12+
"""
13+
14+
def __init__(self, yaml_file: str, client: OpenAI):
15+
"""
16+
Initializes the YamlFileAssistant with a specified YAML file and OpenAI client.
17+
18+
Args:
19+
yaml_file (str): The path to the YAML file to be analyzed.
20+
client (OpenAI): The OpenAI client used to interact with the OpenAI API.
21+
"""
22+
self.yaml_file: str = yaml_file
23+
self.client: OpenAI = client
24+
25+
def run(self, recorded_note: str) -> None:
26+
"""
27+
Runs the assistant to analyze the YAML file based on a recorded note.
28+
29+
This method would typically interact with OpenAI's API to create an assistant,
30+
upload the YAML file, analyze its contents, and generate responses. However, the
31+
actual implementation is currently commented out.
32+
33+
Args:
34+
recorded_note (str): A string containing the note or instructions for analysis.
35+
36+
Note:
37+
The current implementation is commented out and serves as a placeholder for
38+
integrating with OpenAI's API. Uncomment and modify the code as needed.
39+
"""
40+
'''
41+
assistant = self.client.beta.assistants.create(
42+
name="Yaml File Analysis Assistant",
43+
instructions="You are an OpenAPI specification analyst. Use your knowledge to check "
44+
f"if the following information is contained in the provided yaml file. Information: {recorded_note}",
45+
model="gpt-4o",
46+
tools=[{"type": "file_search"}],
47+
)
48+
49+
# Create a vector store called "Financial Statements"
50+
vector_store = self.client.beta.vector_stores.create(name="Financial Statements")
51+
52+
# Ready the files for upload to OpenAI
53+
file_streams = [open(self.yaml_file, "rb")]
54+
55+
# Use the upload and poll SDK helper to upload the files, add them to the vector store,
56+
# and poll the status of the file batch for completion.
57+
file_batch = self.client.beta.vector_stores.file_batches.upload_and_poll(
58+
vector_store_id=vector_store.id, files=file_streams
59+
)
60+
61+
# You can print the status and the file counts of the batch to see the result of this operation.
62+
print(file_batch.status)
63+
print(file_batch.file_counts)
64+
65+
assistant = self.client.beta.assistants.update(
66+
assistant_id=assistant.id,
67+
tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}},
68+
)
69+
70+
# Upload the user-provided file to OpenAI
71+
message_file = self.client.files.create(
72+
file=open("edgar/aapl-10k.pdf", "rb"), purpose="assistants"
73+
)
74+
75+
# Create a thread and attach the file to the message
76+
thread = self.client.beta.threads.create(
77+
messages=[
78+
{
79+
"role": "user",
80+
"content": "How many shares of AAPL were outstanding at the end of October 2023?",
81+
# Attach the new file to the message.
82+
"attachments": [
83+
{"file_id": message_file.id, "tools": [{"type": "file_search"}]}
84+
],
85+
}
86+
]
87+
)
88+
89+
# The thread now has a vector store with that file in its tool resources.
90+
print(thread.tool_resources.file_search)
91+
'''
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import os
2+
from datetime import datetime
3+
import uuid
4+
from typing import List
5+
from enum import Enum
6+
7+
class ReportHandler:
8+
"""
9+
A handler for creating and managing report files that document operations and data.
10+
11+
Attributes:
12+
file_path (str): The path to the directory where report files are stored.
13+
report_name (str): The full path to the current report file being written to.
14+
report (file): The file object for the report, opened for writing data.
15+
"""
16+
17+
def __init__(self):
18+
"""
19+
Initializes the ReportHandler by setting up the file path for reports,
20+
creating the directory if it does not exist, and preparing a new report file.
21+
"""
22+
current_path: str = os.path.dirname(os.path.abspath(__file__))
23+
self.file_path: str = os.path.join(current_path, "reports")
24+
25+
if not os.path.exists(self.file_path):
26+
os.mkdir(self.file_path)
27+
28+
self.report_name: str = os.path.join(self.file_path, f"report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt")
29+
try:
30+
self.report = open(self.report_name, "x")
31+
except FileExistsError:
32+
# Retry with a different name using a UUID to ensure uniqueness
33+
self.report_name = os.path.join(self.file_path,
34+
f"report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_{uuid.uuid4().hex}.txt")
35+
self.report = open(self.report_name, "x")
36+
37+
def write_endpoint_to_report(self, endpoint: str) -> None:
38+
"""
39+
Writes an endpoint string to the report file.
40+
41+
Args:
42+
endpoint (str): The endpoint information to be recorded in the report.
43+
"""
44+
with open(self.report_name, 'a') as report:
45+
report.write(f'{endpoint}\n')
46+
47+
def write_analysis_to_report(self, analysis: List[str], purpose: Enum) -> None:
48+
"""
49+
Writes an analysis result and its purpose to the report file.
50+
51+
Args:
52+
analysis (List[str]): The analysis data to be recorded.
53+
purpose (Enum): An enumeration that describes the purpose of the analysis.
54+
"""
55+
with open(self.report_name, 'a') as report:
56+
report.write(f'{purpose.name}:\n')
57+
for item in analysis:
58+
for line in item.split("\n"):
59+
if "note recorded" in line:
60+
continue
61+
else:
62+
report.write(line + "\n")

0 commit comments

Comments
 (0)