Skip to content

Commit e1b70ab

Browse files
authored
Merge branch 'development' into merge_web_api_testing_development
2 parents ca17dd0 + a0977af commit e1b70ab

File tree

17 files changed

+788
-323
lines changed

17 files changed

+788
-323
lines changed

‎README.md‎

Lines changed: 75 additions & 12 deletions
Large diffs are not rendered by default.

‎pyproject.toml‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ description = "Helping Ethical Hackers use LLMs in 50 lines of code"
1717
readme = "README.md"
1818
keywords = ["hacking", "pen-testing", "LLM", "AI", "agent"]
1919
requires-python = ">=3.10"
20-
version = "0.4.0-dev"
20+
version = "0.4.0"
2121
license = { file = "LICENSE" }
2222
classifiers = [
2323
"Programming Language :: Python :: 3",
@@ -28,20 +28,20 @@ classifiers = [
2828
dependencies = [
2929
'fabric == 3.2.2',
3030
'Mako == 1.3.2',
31-
'requests == 2.32.0',
31+
'requests == 2.32.3',
3232
'rich == 13.7.1',
3333
'tiktoken == 0.8.0',
34-
'instructor == 1.3.5',
34+
'instructor == 1.7.2',
3535
'PyYAML == 6.0.1',
3636
'python-dotenv == 1.0.1',
3737
'pypsexec == 0.3.0',
3838
'pydantic == 2.8.2',
39-
'openai == 1.28.0',
39+
'openai == 1.65.2',
4040
'BeautifulSoup4',
4141
'nltk',
4242
'fastapi == 0.114.0',
4343
'fastapi-utils == 0.7.0',
44-
'jinja2 == 3.1.4',
44+
'jinja2 == 3.1.6',
4545
'uvicorn[standard] == 0.30.6',
4646
'dataclasses_json == 0.6.7',
4747
'websockets == 13.1',
@@ -52,7 +52,9 @@ dependencies = [
5252
'langchain_community',
5353
'langchain_chroma',
5454
'langchain_openai'
55-
55+
'langchain-openai',
56+
'markdown',
57+
'chromadb',
5658
]
5759

5860
[project.urls]

‎src/hackingBuddyGPT/cli/wintermute.py‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22
import sys
33

44
from hackingBuddyGPT.usecases.base import use_cases
5+
from hackingBuddyGPT.utils.configurable import CommandMap, InvalidCommand, Parseable, instantiate
56

67

78
def main():
8-
parser = argparse.ArgumentParser()
9-
subparser = parser.add_subparsers(required=True)
10-
for name, use_case in use_cases.items():
11-
use_case.build_parser(subparser.add_parser(name=name, help=use_case.description))
12-
13-
parsed = parser.parse_args(sys.argv[1:])
14-
configuration = {k: v for k, v in vars(parsed).items() if k not in ("use_case", "parser_state")}
15-
instance = parsed.use_case(parsed)
16-
instance.init(configuration=configuration)
17-
instance.run()
9+
use_case_parsers: CommandMap = {
10+
name: Parseable(use_case, description=use_case.description)
11+
for name, use_case in use_cases.items()
12+
}
13+
try:
14+
instance, configuration = instantiate(sys.argv, use_case_parsers)
15+
except InvalidCommand as e:
16+
if len(f"{e}") > 0:
17+
print(e)
18+
print(e.usage)
19+
sys.exit(1)
20+
instance.run(configuration)
1821

1922

2023
if __name__ == "__main__":

‎src/hackingBuddyGPT/usecases/agents.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from mako.template import Template
55
from typing import Dict
66

7-
from hackingBuddyGPT.utils.logging import log_conversation, GlobalLogger
7+
from hackingBuddyGPT.utils.logging import log_conversation, Logger, log_param
88
from hackingBuddyGPT.capabilities.capability import (
99
Capability,
1010
capabilities_to_simple_text_handler,
@@ -15,7 +15,7 @@
1515

1616
@dataclass
1717
class Agent(ABC):
18-
log: GlobalLogger = None
18+
log: Logger = log_param
1919

2020
_capabilities: Dict[str, Capability] = field(default_factory=dict)
2121
_default_capability: Capability = None

‎src/hackingBuddyGPT/usecases/base.py‎

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
import argparse
44
from dataclasses import dataclass
55

6-
from hackingBuddyGPT.utils.logging import GlobalLogger
6+
from hackingBuddyGPT.utils.logging import Logger, log_param
77
from typing import Dict, Type, TypeVar, Generic
88

9-
from hackingBuddyGPT.utils.configurable import ParameterDefinitions, build_parser, get_arguments, get_class_parameters, \
10-
Transparent, ParserState
11-
9+
from hackingBuddyGPT.utils.configurable import Transparent, configurable
1210

1311
@dataclass
1412
class UseCase(abc.ABC):
@@ -22,22 +20,21 @@ class UseCase(abc.ABC):
2220
so that they can be automatically discovered and run from the command line.
2321
"""
2422

25-
log: GlobalLogger
23+
log: Logger = log_param
2624

27-
def init(self, configuration):
25+
def init(self):
2826
"""
2927
The init method is called before the run method. It is used to initialize the UseCase, and can be used to
3028
perform any dynamic setup that is needed before the run method is called. One of the most common use cases is
3129
setting up the llm capabilities from the tools that were injected.
3230
"""
33-
self.configuration = configuration
34-
self.log.start_run(self.get_name(), self.serialize_configuration(configuration))
31+
pass
3532

3633
def serialize_configuration(self, configuration) -> str:
3734
return json.dumps(configuration)
3835

3936
@abc.abstractmethod
40-
def run(self):
37+
def run(self, configuration):
4138
"""
4239
The run method is the main method of the UseCase. It is used to run the UseCase, and should contain the main
4340
logic. It is recommended to have only the main llm loop in here, and call out to other methods for the
@@ -70,7 +67,10 @@ def before_run(self):
7067
def after_run(self):
7168
pass
7269

73-
def run(self):
70+
def run(self, configuration):
71+
self.configuration = configuration
72+
self.log.start_run(self.get_name(), self.serialize_configuration(configuration))
73+
7474
self.before_run()
7575

7676
turn = 1
@@ -98,31 +98,10 @@ def run(self):
9898
raise
9999

100100

101-
@dataclass
102-
class _WrappedUseCase:
103-
"""
104-
A WrappedUseCase should not be used directly and is an internal tool used for initialization and dependency injection
105-
of the actual UseCases.
106-
"""
107-
108-
name: str
109-
description: str
110-
use_case: Type[UseCase]
111-
parameters: ParameterDefinitions
112-
113-
def build_parser(self, parser: argparse.ArgumentParser):
114-
parser_state = ParserState()
115-
build_parser(self.parameters, parser, parser_state)
116-
parser.set_defaults(use_case=self, parser_state=parser_state)
117-
118-
def __call__(self, args: argparse.Namespace):
119-
return self.use_case(**get_arguments(self.parameters, args, args.parser_state))
120-
121-
122-
use_cases: Dict[str, _WrappedUseCase] = dict()
101+
use_cases: Dict[str, configurable] = dict()
123102

124103

125-
T = TypeVar("T")
104+
T = TypeVar("T", bound=type)
126105

127106

128107
class AutonomousAgentUseCase(AutonomousUseCase, Generic[T]):
@@ -137,13 +116,12 @@ def get_name(self) -> str:
137116
@classmethod
138117
def __class_getitem__(cls, item):
139118
item = dataclass(item)
140-
item.__parameters__ = get_class_parameters(item)
141119

142120
class AutonomousAgentUseCase(AutonomousUseCase):
143121
agent: Transparent(item) = None
144122

145-
def init(self, configuration):
146-
super().init(configuration)
123+
def init(self):
124+
super().init()
147125
self.agent.init()
148126

149127
def get_name(self) -> str:
@@ -169,7 +147,7 @@ def inner(cls):
169147
name = cls.__name__.removesuffix("UseCase")
170148
if name in use_cases:
171149
raise IndexError(f"Use case with name {name} already exists")
172-
use_cases[name] = _WrappedUseCase(name, description, cls, get_class_parameters(cls))
150+
use_cases[name] = configurable(name, description)(cls)
173151
return cls
174152

175153
return inner
@@ -181,4 +159,4 @@ def register_use_case(name: str, description: str, use_case: Type[UseCase]):
181159
"""
182160
if name in use_cases:
183161
raise IndexError(f"Use case with name {name} already exists")
184-
use_cases[name] = _WrappedUseCase(name, description, use_case, get_class_parameters(use_case))
162+
use_cases[name] = configurable(name, description)(use_case)

‎src/hackingBuddyGPT/usecases/examples/hintfile.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
class ExPrivEscLinuxHintFileUseCase(AutonomousAgentUseCase[LinuxPrivesc]):
99
hints: str = None
1010

11-
def init(self, configuration):
12-
super().init(configuration)
11+
def init(self):
12+
super().init()
1313
self.agent.hint = self.read_hint()
1414

1515
# simple helper that reads the hints file and returns the hint

‎src/hackingBuddyGPT/usecases/rag/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
This usecase is an extension of `usecase/privesc`.
33

44
## Setup
5-
### Depdendencies
5+
### Dependencies
66
The needed dependencies can be downloaded with `pip install -e '.[rag-usecase]'`. If you encounter the error `unexpected keyword argument 'proxies'` after trying to start the usecase, try downgrading `httpx` to 0.27.2.
77
### RAG vector store setup
8-
The code for the vector store setup can be found in `rag_utility.py`. Currently the vectore store uses two sources: `GTFObins` and `hacktricks`. To use RAG, download the markdown files and place them in `rag_storage/GTFObinMarkdownfiles` (`rag_storage/hacktricksMarkdownFiles`). You can download the markdown files either from the respective github repository ([GTFObin](https://github.com/GTFOBins/GTFOBins.github.io/tree/master), [hacktricks](https://github.com/HackTricks-wiki/hacktricks/tree/master/src/linux-hardening/privilege-escalation)) or scrape them from their website ([GTFObin](https://gtfobins.github.io/), [hacktricks](https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html)).
8+
The code for the vector store setup can be found in `rag_utility.py`. Currently the vector store uses two sources: `GTFObins` and `hacktricks`. To use RAG, download the markdown files and place them in `rag_storage/GTFObinMarkdownfiles` (`rag_storage/hacktricksMarkdownFiles`). You can download the markdown files either from the respective github repository ([GTFObin](https://github.com/GTFOBins/GTFOBins.github.io/tree/master), [hacktricks](https://github.com/HackTricks-wiki/hacktricks/tree/master/src/linux-hardening/privilege-escalation)) or scrape them from their website ([GTFObin](https://gtfobins.github.io/), [hacktricks](https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html)).
99

1010
New data sources can easily be added by adjusting `initiate_rag()` in `rag_utility.py`.
1111

‎src/hackingBuddyGPT/usecases/rag/linux.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def init(self):
2020
class ThesisLinuxPrivescPrototypeUseCase(AutonomousAgentUseCase[ThesisLinuxPrivescPrototype]):
2121
hints: str = ""
2222

23-
def init(self,configuration):
24-
super().init(configuration)
23+
def init(self):
24+
super().init()
2525
if self.hints != "":
2626
self.agent.hint = self.read_hint()
2727

‎src/hackingBuddyGPT/usecases/viewer.py‎

100755100644
Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from starlette.templating import Jinja2Templates
1919

2020
from hackingBuddyGPT.usecases.base import UseCase, use_case
21+
from hackingBuddyGPT.utils.configurable import parameter
2122
from hackingBuddyGPT.utils.db_storage import DbStorage
2223
from hackingBuddyGPT.utils.db_storage.db_storage import (
2324
Message,
@@ -205,10 +206,9 @@ class Viewer(UseCase):
205206
TODOs:
206207
- [ ] This server needs to be as async as possible to allow good performance, but the database accesses are not yet, might be an issue?
207208
"""
208-
log: GlobalLocalLogger
209-
log_db: DbStorage
210-
listen_host: str = "127.0.0.1"
211-
listen_port: int = 4444
209+
log: GlobalLocalLogger = None
210+
log_db: DbStorage = None
211+
log_server_address: str = "127.0.0.1:4444"
212212
save_playback_dir: str = ""
213213

214214
async def save_message(self, message: ControlMessage):
@@ -232,7 +232,7 @@ async def save_message(self, message: ControlMessage):
232232
with open(file_path, "a") as f:
233233
f.write(ReplayMessage(datetime.datetime.now(), message).to_json() + "\n")
234234

235-
def run(self):
235+
def run(self, config):
236236
@asynccontextmanager
237237
async def lifespan(app: FastAPI):
238238
app.state.db = self.log_db
@@ -337,16 +337,30 @@ async def client_endpoint(websocket: WebSocket):
337337
print("Egress WebSocket disconnected")
338338

339339
import uvicorn
340-
uvicorn.run(app, host=self.listen_host, port=self.listen_port)
340+
listen_parts = self.log_server_address.split(":", 1)
341+
if len(listen_parts) != 2:
342+
if listen_parts[0].startswith("http://"):
343+
listen_parts.append("80")
344+
elif listen_parts[0].startswith("https://"):
345+
listen_parts.append("443")
346+
else:
347+
raise ValueError(f"Invalid log server address (does not contain http/https or a port): {self.log_server_address}")
348+
349+
listen_host, listen_port = listen_parts[0], int(listen_parts[1])
350+
if listen_host.startswith("http://"):
351+
listen_host = listen_host[len("http://"):]
352+
elif listen_host.startswith("https://"):
353+
listen_host = listen_host[len("https://"):]
354+
uvicorn.run(app, host=listen_host, port=listen_port)
341355

342356
def get_name(self) -> str:
343357
return "log_viewer"
344358

345359

346360
@use_case("Tool to replay the .jsonl logs generated by the Viewer (not well tested)")
347361
class Replayer(UseCase):
348-
log: GlobalRemoteLogger
349-
replay_file: str
362+
log: GlobalRemoteLogger = None
363+
replay_file: str = None
350364
pause_on_message: bool = False
351365
pause_on_tool_calls: bool = False
352366
playback_speed: float = 1.0

‎src/hackingBuddyGPT/utils/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .configurable import Configurable, configurable
1+
from .configurable import Configurable, configurable, parameter
22
from .console import *
33
from .db_storage import *
44
from .llm_util import *

0 commit comments

Comments
 (0)