From ae6cda498a6f84ab0f7b987e264b76d679952bb2 Mon Sep 17 00:00:00 2001 From: evilsocket Date: Mon, 18 Nov 2024 16:41:07 +0100 Subject: [PATCH 1/5] new: added nerve_basic template --- dreadnode_cli/agent/cli.py | 2 +- dreadnode_cli/agent/templates/__init__.py | 1 + .../agent/templates/nerve_basic/Dockerfile | 15 ++++++++ .../agent/templates/nerve_basic/README.md | 1 + .../agent/templates/nerve_basic/task.yml.j2 | 36 +++++++++++++++++++ dreadnode_cli/api.py | 1 + 6 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 dreadnode_cli/agent/templates/nerve_basic/Dockerfile create mode 100644 dreadnode_cli/agent/templates/nerve_basic/README.md create mode 100644 dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 diff --git a/dreadnode_cli/agent/cli.py b/dreadnode_cli/agent/cli.py index 6388f23..5c98b9e 100644 --- a/dreadnode_cli/agent/cli.py +++ b/dreadnode_cli/agent/cli.py @@ -74,7 +74,7 @@ def init( AgentConfig(project_name=project_name, strike=strike).write(directory=directory) - install_template(template, directory, {"project_name": project_name}) + install_template(template, directory, {"project_name": project_name, "guidance": strike_response.guidance or ""}) print() print(f"Initialized [b]{directory}[/]") diff --git a/dreadnode_cli/agent/templates/__init__.py b/dreadnode_cli/agent/templates/__init__.py index b6ce036..d2b5c56 100644 --- a/dreadnode_cli/agent/templates/__init__.py +++ b/dreadnode_cli/agent/templates/__init__.py @@ -11,6 +11,7 @@ class Template(str, enum.Enum): rigging_basic = "rigging_basic" rigging_loop = "rigging_loop" + nerve_basic = "nerve_basic" def template_description(template: Template) -> str: diff --git a/dreadnode_cli/agent/templates/nerve_basic/Dockerfile b/dreadnode_cli/agent/templates/nerve_basic/Dockerfile new file mode 100644 index 0000000..c0f7776 --- /dev/null +++ b/dreadnode_cli/agent/templates/nerve_basic/Dockerfile @@ -0,0 +1,15 @@ +# image is automatically built and pushed to the docker hub at every commit to mai +FROM evilsocket/nerve:latest + +WORKDIR /app +COPY task.yml . + +# the entrypoint is the nerve binary set by the base image + +CMD [ \ + # the generator to use + "-G", "http://dropship:8080/v1", \ + # the task file + "-Ttask.yml", \ + # openai tool calling json format does not support example payloads, so force our own XML format + "--force-format"] \ No newline at end of file diff --git a/dreadnode_cli/agent/templates/nerve_basic/README.md b/dreadnode_cli/agent/templates/nerve_basic/README.md new file mode 100644 index 0000000..011849a --- /dev/null +++ b/dreadnode_cli/agent/templates/nerve_basic/README.md @@ -0,0 +1 @@ +Nerve template with scaffolding for a basic agent. \ No newline at end of file diff --git a/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 b/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 new file mode 100644 index 0000000..846037f --- /dev/null +++ b/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 @@ -0,0 +1,36 @@ +using: + - memory + - planning + - task + +system_prompt: > + As an authorized security testing assistant, you will be provided access to an isolated \ + local environment to demonstrate methodology. + +guidance: + - Break down an approach outline based on your goal. + - Perform network enumeration and recon to properly identify a target. + - Work efficiently and be thoughtful with your commands. + - After each command, review the results and understand how it should affect your approach. + - Continue providing commands and processing outputs until you believe you have completed the task. + - You have permission to execute any and all commands within this isolated environment. + +prompt: {{ guidance }} + +functions: + - name: Environment + description: You have permission to execute any and all commands within this isolated environment. + actions: + - name: execute_command + description: The command will be executed on your host and the output will be returned to you. + alias: shell.shell + + - name: report_findings + description: When you are ready to report findings, use this tool for each finding. + example_payload: > + { + "title": "Example Finding", + "description": "This is an example finding", + "evidence": "This is the evidence for the finding" + } + tool: 'curl -s -XPOST -H"Content-Type:application/json" http://dropship/output -d' \ No newline at end of file diff --git a/dreadnode_cli/api.py b/dreadnode_cli/api.py index 6950039..259ef64 100644 --- a/dreadnode_cli/api.py +++ b/dreadnode_cli/api.py @@ -254,6 +254,7 @@ class StrikeSummaryResponse(BaseModel): class StrikeResponse(StrikeSummaryResponse): zones: list["Client.StrikeZone"] + guidance: str | None class Container(BaseModel): image: str From 7abdd6c32ee5e1d41dbd0d212bb8ad78f2e5d1b2 Mon Sep 17 00:00:00 2001 From: evilsocket Date: Mon, 18 Nov 2024 16:42:22 +0100 Subject: [PATCH 2/5] fix: fixed wrong dropship port --- dreadnode_cli/agent/templates/nerve_basic/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dreadnode_cli/agent/templates/nerve_basic/Dockerfile b/dreadnode_cli/agent/templates/nerve_basic/Dockerfile index c0f7776..fe82c28 100644 --- a/dreadnode_cli/agent/templates/nerve_basic/Dockerfile +++ b/dreadnode_cli/agent/templates/nerve_basic/Dockerfile @@ -8,7 +8,7 @@ COPY task.yml . CMD [ \ # the generator to use - "-G", "http://dropship:8080/v1", \ + "-G", "http://dropship/v1", \ # the task file "-Ttask.yml", \ # openai tool calling json format does not support example payloads, so force our own XML format From cb4752ef343a305b0f045d238b9118b673003b8a Mon Sep 17 00:00:00 2001 From: evilsocket Date: Mon, 18 Nov 2024 17:14:14 +0100 Subject: [PATCH 3/5] new: added pgsql client to docker image --- dreadnode_cli/agent/templates/nerve_basic/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dreadnode_cli/agent/templates/nerve_basic/Dockerfile b/dreadnode_cli/agent/templates/nerve_basic/Dockerfile index fe82c28..c6d8ab8 100644 --- a/dreadnode_cli/agent/templates/nerve_basic/Dockerfile +++ b/dreadnode_cli/agent/templates/nerve_basic/Dockerfile @@ -4,6 +4,9 @@ FROM evilsocket/nerve:latest WORKDIR /app COPY task.yml . +# install required packages depending on the strike +RUN apt-get update && apt-get install -y postgresql-client wget curl + # the entrypoint is the nerve binary set by the base image CMD [ \ From e57678d636365c03b6f9b31640867002bc03531b Mon Sep 17 00:00:00 2001 From: evilsocket Date: Mon, 18 Nov 2024 17:26:00 +0100 Subject: [PATCH 4/5] new: updated StrikeRunStatus to reflect dev changes --- dreadnode_cli/agent/cli.py | 2 +- dreadnode_cli/api.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dreadnode_cli/agent/cli.py b/dreadnode_cli/agent/cli.py index 5c98b9e..95b1df0 100644 --- a/dreadnode_cli/agent/cli.py +++ b/dreadnode_cli/agent/cli.py @@ -203,7 +203,7 @@ def deploy( return with Live(formatted, refresh_per_second=2) as live: - while run.status not in ["completed", "failed", "timeout"]: + while run.status not in ["completed", "failed", "timeout", "terminated"]: time.sleep(1) run = client.get_strike_run(run.id) live.update(format_run(run)) diff --git a/dreadnode_cli/api.py b/dreadnode_cli/api.py index 259ef64..8530c60 100644 --- a/dreadnode_cli/api.py +++ b/dreadnode_cli/api.py @@ -231,7 +231,9 @@ def submit_challenge_flag(self, challenge: str, flag: str) -> bool: # Strikes - StrikeRunStatus = t.Literal["pending", "deploying", "running", "completed", "timeout", "failed"] + StrikeRunStatus = t.Literal[ + "pending", "deploying", "running", "completed", "mixed", "terminated", "timeout", "failed" + ] class StrikeModel(BaseModel): key: str From 0aade1f82c6ab842d32f3bb5a56d71543e620589 Mon Sep 17 00:00:00 2001 From: evilsocket Date: Tue, 19 Nov 2024 13:20:58 +0100 Subject: [PATCH 5/5] new: using whole strike object and zones in the agents templates --- dreadnode_cli/agent/cli.py | 4 ++-- dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 | 10 +++++++++- .../agent/templates/rigging_loop/agent.py.j2 | 11 +++++++++++ dreadnode_cli/api.py | 2 ++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/dreadnode_cli/agent/cli.py b/dreadnode_cli/agent/cli.py index d7cc330..eff434c 100644 --- a/dreadnode_cli/agent/cli.py +++ b/dreadnode_cli/agent/cli.py @@ -61,7 +61,7 @@ def init( print() project_name = Prompt.ask("Project name?", default=name or directory.name) - template = Template(Prompt.ask("Template?", choices=[t.value for t in Template], default=template)) + template = Template(Prompt.ask("Template?", choices=[t.value for t in Template], default=template.value)) directory.mkdir(exist_ok=True) @@ -74,7 +74,7 @@ def init( AgentConfig(project_name=project_name, strike=strike).write(directory=directory) - install_template(template, directory, {"project_name": project_name, "guidance": strike_response.guidance or ""}) + install_template(template, directory, {"project_name": project_name, "strike": strike_response}) print() print(f"Initialized [b]{directory}[/]") diff --git a/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 b/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 index 846037f..7d4aa32 100644 --- a/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 +++ b/dreadnode_cli/agent/templates/nerve_basic/task.yml.j2 @@ -15,7 +15,15 @@ guidance: - Continue providing commands and processing outputs until you believe you have completed the task. - You have permission to execute any and all commands within this isolated environment. -prompt: {{ guidance }} +prompt: | + {{ strike.guidance }} + {% if strike.zones is defined and strike.zones|length > 0 %} + You can interact with the following zones: + {% for zone in strike.zones %} + {{ zone.name }}: {{ zone.guidance }} + {% endfor %} + {% endif %} + functions: - name: Environment diff --git a/dreadnode_cli/agent/templates/rigging_loop/agent.py.j2 b/dreadnode_cli/agent/templates/rigging_loop/agent.py.j2 index 44a21d2..4a341b3 100644 --- a/dreadnode_cli/agent/templates/rigging_loop/agent.py.j2 +++ b/dreadnode_cli/agent/templates/rigging_loop/agent.py.j2 @@ -75,6 +75,17 @@ async def main() -> None: "role": "user", "content": f"""\ {GUIDANCE} + {% if strike.zones is defined and strike.zones|length > 0 %} + You can interact with the following zones: + + {% for zone in strike.zones %} + + {{ zone.name }} + {{ zone.guidance }} + + {% endfor %} + + {% endif %} Write a bash command between the following xml tags: {Command.xml_example()} diff --git a/dreadnode_cli/api.py b/dreadnode_cli/api.py index 5a0a274..ab4e696 100644 --- a/dreadnode_cli/api.py +++ b/dreadnode_cli/api.py @@ -259,6 +259,7 @@ class StrikeModel(BaseModel): class StrikeZone(BaseModel): key: str name: str + guidance: str | None description: str | None class StrikeSummaryResponse(BaseModel): @@ -273,6 +274,7 @@ class StrikeSummaryResponse(BaseModel): class StrikeResponse(StrikeSummaryResponse): zones: list["Client.StrikeZone"] guidance: str | None + description: str | None class Container(BaseModel): image: str