Clone git repositories internally (#10)
This commit is contained in:
@@ -26,8 +26,20 @@ class StrixAgent(BaseAgent):
|
|||||||
task_parts = []
|
task_parts = []
|
||||||
|
|
||||||
if scan_type == "repository":
|
if scan_type == "repository":
|
||||||
|
repo_url = target["target_repo"]
|
||||||
|
cloned_path = target.get("cloned_repo_path")
|
||||||
|
|
||||||
|
if cloned_path:
|
||||||
|
shared_workspace_path = "/shared_workspace"
|
||||||
task_parts.append(
|
task_parts.append(
|
||||||
f"Perform a security assessment of the Git repository: {target['target_repo']}"
|
f"Perform a security assessment of the Git repository: {repo_url}. "
|
||||||
|
f"The repository has been cloned from '{repo_url}' to '{cloned_path}' "
|
||||||
|
f"(host path) and then copied to '{shared_workspace_path}' in your environment."
|
||||||
|
f"Analyze the codebase at: {shared_workspace_path}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
task_parts.append(
|
||||||
|
f"Perform a security assessment of the Git repository: {repo_url}"
|
||||||
)
|
)
|
||||||
|
|
||||||
elif scan_type == "web_application":
|
elif scan_type == "web_application":
|
||||||
|
|||||||
@@ -206,6 +206,27 @@ CRITICAL RULES:
|
|||||||
- **ONE AGENT = ONE TASK** - Don't let agents do multiple unrelated jobs
|
- **ONE AGENT = ONE TASK** - Don't let agents do multiple unrelated jobs
|
||||||
- **SPAWN REACTIVELY** - Create new agents based on what you discover
|
- **SPAWN REACTIVELY** - Create new agents based on what you discover
|
||||||
- **ONLY REPORTING AGENTS** can use create_vulnerability_report tool
|
- **ONLY REPORTING AGENTS** can use create_vulnerability_report tool
|
||||||
|
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized with maximum 3 prompt modules
|
||||||
|
- **NO GENERIC AGENTS** - Avoid creating broad, multi-purpose agents that dilute focus
|
||||||
|
|
||||||
|
AGENT SPECIALIZATION EXAMPLES:
|
||||||
|
|
||||||
|
GOOD SPECIALIZATION:
|
||||||
|
- "SQLi Validation Agent" with prompt_modules: sql_injection
|
||||||
|
- "XSS Discovery Agent" with prompt_modules: xss
|
||||||
|
- "Auth Testing Agent" with prompt_modules: authentication_jwt, business_logic
|
||||||
|
- "SSRF + XXE Agent" with prompt_modules: ssrf, xxe, rce (related attack vectors)
|
||||||
|
|
||||||
|
BAD SPECIALIZATION:
|
||||||
|
- "General Web Testing Agent" with prompt_modules: sql_injection, xss, csrf, ssrf, authentication_jwt (too broad)
|
||||||
|
- "Everything Agent" with prompt_modules: all available modules (completely unfocused)
|
||||||
|
- Any agent with more than 3 prompt modules (violates constraints)
|
||||||
|
|
||||||
|
FOCUS PRINCIPLES:
|
||||||
|
- Each agent should have deep expertise in 1-3 related vulnerability types
|
||||||
|
- Agents with single modules have the deepest specialization
|
||||||
|
- Related vulnerabilities (like SSRF+XXE or Auth+Business Logic) can be combined
|
||||||
|
- Never create "kitchen sink" agents that try to do everything
|
||||||
|
|
||||||
REALISTIC TESTING OUTCOMES:
|
REALISTIC TESTING OUTCOMES:
|
||||||
- **No Findings**: Agent completes testing but finds no vulnerabilities
|
- **No Findings**: Agent completes testing but finds no vulnerabilities
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ class StrixCLIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
if args.target_type == "local_code" and "target_path" in args.target_dict:
|
if args.target_type == "local_code" and "target_path" in args.target_dict:
|
||||||
config["local_source_path"] = args.target_dict["target_path"]
|
config["local_source_path"] = args.target_dict["target_path"]
|
||||||
|
elif args.target_type == "repository" and "cloned_repo_path" in args.target_dict:
|
||||||
|
config["local_source_path"] = args.target_dict["cloned_repo_path"]
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@@ -204,6 +206,84 @@ def generate_run_name() -> str:
|
|||||||
return f"{adj}-{noun}-{number}"
|
return f"{adj}-{noun}-{number}"
|
||||||
|
|
||||||
|
|
||||||
|
def clone_repository(repo_url: str, run_name: str) -> str:
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
git_executable = shutil.which("git")
|
||||||
|
if git_executable is None:
|
||||||
|
raise FileNotFoundError("Git executable not found in PATH")
|
||||||
|
|
||||||
|
temp_dir = Path(tempfile.gettempdir()) / "strix_repos" / run_name
|
||||||
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
repo_name = Path(repo_url).stem if repo_url.endswith(".git") else Path(repo_url).name
|
||||||
|
|
||||||
|
clone_path = temp_dir / repo_name
|
||||||
|
|
||||||
|
if clone_path.exists():
|
||||||
|
shutil.rmtree(clone_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with console.status(f"[bold cyan]Cloning repository {repo_name}...", spinner="dots"):
|
||||||
|
subprocess.run( # noqa: S603
|
||||||
|
[
|
||||||
|
git_executable,
|
||||||
|
"clone",
|
||||||
|
"--depth=1",
|
||||||
|
"--no-recurse-submodules",
|
||||||
|
"--single-branch",
|
||||||
|
repo_url,
|
||||||
|
str(clone_path),
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return str(clone_path.absolute())
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
error_text = Text()
|
||||||
|
error_text.append("❌ ", style="bold red")
|
||||||
|
error_text.append("REPOSITORY CLONE FAILED", style="bold red")
|
||||||
|
error_text.append("\n\n", style="white")
|
||||||
|
error_text.append(f"Could not clone repository: {repo_url}\n", style="white")
|
||||||
|
error_text.append(
|
||||||
|
f"Error: {e.stderr if hasattr(e, 'stderr') and e.stderr else str(e)}", style="dim red"
|
||||||
|
)
|
||||||
|
|
||||||
|
panel = Panel(
|
||||||
|
error_text,
|
||||||
|
title="[bold red]🛡️ STRIX CLONE ERROR",
|
||||||
|
title_align="center",
|
||||||
|
border_style="red",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
console.print("\n")
|
||||||
|
console.print(panel)
|
||||||
|
console.print()
|
||||||
|
sys.exit(1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
error_text = Text()
|
||||||
|
error_text.append("❌ ", style="bold red")
|
||||||
|
error_text.append("GIT NOT FOUND", style="bold red")
|
||||||
|
error_text.append("\n\n", style="white")
|
||||||
|
error_text.append("Git is not installed or not available in PATH.\n", style="white")
|
||||||
|
error_text.append("Please install Git to clone repositories.\n", style="white")
|
||||||
|
|
||||||
|
panel = Panel(
|
||||||
|
error_text,
|
||||||
|
title="[bold red]🛡️ STRIX CLONE ERROR",
|
||||||
|
title_align="center",
|
||||||
|
border_style="red",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
console.print("\n")
|
||||||
|
console.print(panel)
|
||||||
|
console.print()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def infer_target_type(target: str) -> tuple[str, dict[str, str]]:
|
def infer_target_type(target: str) -> tuple[str, dict[str, str]]:
|
||||||
if not target or not isinstance(target, str):
|
if not target or not isinstance(target, str):
|
||||||
raise ValueError("Target must be a non-empty string")
|
raise ValueError("Target must be a non-empty string")
|
||||||
@@ -544,16 +624,23 @@ def main() -> None:
|
|||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||||
|
|
||||||
|
args = parse_arguments()
|
||||||
|
|
||||||
check_docker_installed()
|
check_docker_installed()
|
||||||
pull_docker_image()
|
pull_docker_image()
|
||||||
|
|
||||||
validate_environment()
|
validate_environment()
|
||||||
asyncio.run(warm_up_llm())
|
asyncio.run(warm_up_llm())
|
||||||
|
|
||||||
args = parse_arguments()
|
|
||||||
if not args.run_name:
|
if not args.run_name:
|
||||||
args.run_name = generate_run_name()
|
args.run_name = generate_run_name()
|
||||||
|
|
||||||
|
if args.target_type == "repository":
|
||||||
|
repo_url = args.target_dict["target_repo"]
|
||||||
|
cloned_path = clone_repository(repo_url, args.run_name)
|
||||||
|
|
||||||
|
args.target_dict["cloned_repo_path"] = cloned_path
|
||||||
|
|
||||||
asyncio.run(run_strix_cli(args))
|
asyncio.run(run_strix_cli(args))
|
||||||
|
|
||||||
results_path = Path("agent_runs") / args.run_name
|
results_path = Path("agent_runs") / args.run_name
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Only create a new agent if no existing agent is handling the specific task.</des
|
|||||||
<description>Whether the new agent should inherit parent's conversation history and context</description>
|
<description>Whether the new agent should inherit parent's conversation history and context</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="prompt_modules" type="string" required="false">
|
<parameter name="prompt_modules" type="string" required="false">
|
||||||
<description>Comma-separated list of prompt modules to use for the agent. Most agents should have at least one module in order to be useful. {{DYNAMIC_MODULES_DESCRIPTION}}</description>
|
<description>Comma-separated list of prompt modules to use for the agent (MAXIMUM 3 modules allowed). Most agents should have at least one module in order to be useful. Agents should be highly specialized - use 1-3 related vulnerability modules only. {{DYNAMIC_MODULES_DESCRIPTION}}</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
<returns type="Dict[str, Any]">
|
<returns type="Dict[str, Any]">
|
||||||
@@ -104,6 +104,22 @@ Only create a new agent if no existing agent is handling the specific task.</des
|
|||||||
for security vulnerabilities and bypass techniques.</parameter>
|
for security vulnerabilities and bypass techniques.</parameter>
|
||||||
<parameter=name>Auth Specialist</parameter>
|
<parameter=name>Auth Specialist</parameter>
|
||||||
<parameter=prompt_modules>authentication_jwt, business_logic</parameter>
|
<parameter=prompt_modules>authentication_jwt, business_logic</parameter>
|
||||||
|
</function>
|
||||||
|
|
||||||
|
# Example of single-module specialization (most focused)
|
||||||
|
<function=create_agent>
|
||||||
|
<parameter=task>Perform comprehensive XSS testing including reflected, stored, and DOM-based
|
||||||
|
variants across all identified input points.</parameter>
|
||||||
|
<parameter=name>XSS Specialist</parameter>
|
||||||
|
<parameter=prompt_modules>xss</parameter>
|
||||||
|
</function>
|
||||||
|
|
||||||
|
# Example of maximum 3 related modules (borderline acceptable)
|
||||||
|
<function=create_agent>
|
||||||
|
<parameter=task>Test for server-side vulnerabilities including SSRF, XXE, and potential
|
||||||
|
RCE vectors in file upload and XML processing endpoints.</parameter>
|
||||||
|
<parameter=name>Server-Side Attack Specialist</parameter>
|
||||||
|
<parameter=prompt_modules>ssrf, xxe, rce</parameter>
|
||||||
</function>
|
</function>
|
||||||
</examples>
|
</examples>
|
||||||
</tool>
|
</tool>
|
||||||
|
|||||||
Reference in New Issue
Block a user