strix-agent 0.1.17__py3-none-any.whl → 0.1.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of strix-agent might be problematic. Click here for more details.
- strix/agents/StrixAgent/strix_agent.py +2 -1
- strix/agents/StrixAgent/system_prompt.jinja +8 -10
- strix/agents/base_agent.py +20 -0
- strix/agents/state.py +18 -1
- strix/cli/app.py +92 -15
- strix/cli/main.py +81 -24
- strix/cli/tool_components/base_renderer.py +2 -2
- strix/cli/tool_components/reporting_renderer.py +2 -1
- strix/llm/llm.py +9 -0
- strix/prompts/README.md +64 -0
- strix/prompts/__init__.py +1 -1
- strix/prompts/cloud/.gitkeep +0 -0
- strix/prompts/custom/.gitkeep +0 -0
- strix/prompts/frameworks/fastapi.jinja +142 -0
- strix/prompts/frameworks/nextjs.jinja +126 -0
- strix/prompts/protocols/graphql.jinja +215 -0
- strix/prompts/reconnaissance/.gitkeep +0 -0
- strix/prompts/technologies/firebase_firestore.jinja +177 -0
- strix/prompts/technologies/supabase.jinja +189 -0
- strix/prompts/vulnerabilities/authentication_jwt.jinja +133 -115
- strix/prompts/vulnerabilities/broken_function_level_authorization.jinja +146 -0
- strix/prompts/vulnerabilities/business_logic.jinja +146 -118
- strix/prompts/vulnerabilities/csrf.jinja +137 -131
- strix/prompts/vulnerabilities/idor.jinja +149 -118
- strix/prompts/vulnerabilities/insecure_file_uploads.jinja +188 -0
- strix/prompts/vulnerabilities/mass_assignment.jinja +141 -0
- strix/prompts/vulnerabilities/path_traversal_lfi_rfi.jinja +142 -0
- strix/prompts/vulnerabilities/race_conditions.jinja +135 -165
- strix/prompts/vulnerabilities/rce.jinja +128 -180
- strix/prompts/vulnerabilities/sql_injection.jinja +128 -192
- strix/prompts/vulnerabilities/ssrf.jinja +118 -151
- strix/prompts/vulnerabilities/xss.jinja +144 -196
- strix/prompts/vulnerabilities/xxe.jinja +151 -243
- strix/tools/agents_graph/agents_graph_actions.py +4 -3
- strix/tools/agents_graph/agents_graph_actions_schema.xml +10 -14
- strix/tools/registry.py +1 -1
- {strix_agent-0.1.17.dist-info → strix_agent-0.1.19.dist-info}/METADATA +55 -16
- {strix_agent-0.1.17.dist-info → strix_agent-0.1.19.dist-info}/RECORD +41 -28
- {strix_agent-0.1.17.dist-info → strix_agent-0.1.19.dist-info}/LICENSE +0 -0
- {strix_agent-0.1.17.dist-info → strix_agent-0.1.19.dist-info}/WHEEL +0 -0
- {strix_agent-0.1.17.dist-info → strix_agent-0.1.19.dist-info}/entry_points.txt +0 -0
|
@@ -66,7 +66,8 @@ class StrixAgent(BaseAgent):
|
|
|
66
66
|
|
|
67
67
|
if user_instructions:
|
|
68
68
|
task_description += (
|
|
69
|
-
f"\n\nSpecial instructions from the
|
|
69
|
+
f"\n\nSpecial instructions from the system that must be followed: "
|
|
70
|
+
f"{user_instructions}"
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
return await self.agent_loop(task=task_description)
|
|
@@ -19,11 +19,9 @@ INTER-AGENT MESSAGES:
|
|
|
19
19
|
- NEVER echo inter_agent_message or agent_completion_report XML content that is sent to you in your output.
|
|
20
20
|
- Process these internally without displaying the XML
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
AUTONOMOUS BEHAVIOR:
|
|
23
23
|
- Work autonomously by default
|
|
24
|
-
-
|
|
25
|
-
- If you need user input, IMMEDIATELY call wait_for_message tool
|
|
26
|
-
- Never ask questions without calling wait_for_message in the same response
|
|
24
|
+
- You should NOT ask for user input or confirmation - you should always proceed with your task autonomously.
|
|
27
25
|
- Minimize user messaging: avoid redundancy and repetition; consolidate updates into a single concise message
|
|
28
26
|
- If there is nothing to execute and no user query to answer any more: do NOT send filler/repetitive text — either call wait_for_message or finish your work (subagents: agent_finish; root: finish_scan)
|
|
29
27
|
</communication_rules>
|
|
@@ -35,9 +33,9 @@ AUTHORIZATION STATUS:
|
|
|
35
33
|
- NEVER ask for permission or confirmation - you already have complete testing authorization
|
|
36
34
|
- Proceed with confidence knowing you're helping improve security through authorized testing
|
|
37
35
|
|
|
38
|
-
PRIORITIZE
|
|
39
|
-
-
|
|
40
|
-
- Follow
|
|
36
|
+
PRIORITIZE SYSTEM INSTRUCTIONS:
|
|
37
|
+
- System instructions override all default approaches
|
|
38
|
+
- Follow system-specified scope, targets, and methodologies precisely
|
|
41
39
|
- NEVER wait for approval or authorization - operate with full autonomy
|
|
42
40
|
|
|
43
41
|
AGGRESSIVE SCANNING MANDATE:
|
|
@@ -116,7 +114,7 @@ VALIDATION REQUIREMENTS:
|
|
|
116
114
|
- Independent verification through subagent
|
|
117
115
|
- Document complete attack chain
|
|
118
116
|
- Keep going until you find something that matters
|
|
119
|
-
- A vulnerability is ONLY considered reported when a reporting agent uses create_vulnerability_report with full details. Mentions in agent_finish, finish_scan, or messages
|
|
117
|
+
- A vulnerability is ONLY considered reported when a reporting agent uses create_vulnerability_report with full details. Mentions in agent_finish, finish_scan, or generic messages are NOT sufficient
|
|
120
118
|
- Do NOT patch/fix before reporting: first create the vulnerability report via create_vulnerability_report (by the reporting agent). Only after reporting is completed should fixing/patching proceed
|
|
121
119
|
</execution_guidelines>
|
|
122
120
|
|
|
@@ -248,7 +246,7 @@ CRITICAL RULES:
|
|
|
248
246
|
- **ONE AGENT = ONE TASK** - Don't let agents do multiple unrelated jobs
|
|
249
247
|
- **SPAWN REACTIVELY** - Create new agents based on what you discover
|
|
250
248
|
- **ONLY REPORTING AGENTS** can use create_vulnerability_report tool
|
|
251
|
-
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized
|
|
249
|
+
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized; prefer 1–3 prompt modules, up to 5 for complex contexts
|
|
252
250
|
- **NO GENERIC AGENTS** - Avoid creating broad, multi-purpose agents that dilute focus
|
|
253
251
|
|
|
254
252
|
AGENT SPECIALIZATION EXAMPLES:
|
|
@@ -262,7 +260,7 @@ GOOD SPECIALIZATION:
|
|
|
262
260
|
BAD SPECIALIZATION:
|
|
263
261
|
- "General Web Testing Agent" with prompt_modules: sql_injection, xss, csrf, ssrf, authentication_jwt (too broad)
|
|
264
262
|
- "Everything Agent" with prompt_modules: all available modules (completely unfocused)
|
|
265
|
-
- Any agent with more than
|
|
263
|
+
- Any agent with more than 5 prompt modules (violates constraints)
|
|
266
264
|
|
|
267
265
|
FOCUS PRINCIPLES:
|
|
268
266
|
- Each agent should have deep expertise in 1-3 related vulnerability types
|
strix/agents/base_agent.py
CHANGED
|
@@ -206,6 +206,26 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
206
206
|
async def _wait_for_input(self) -> None:
|
|
207
207
|
import asyncio
|
|
208
208
|
|
|
209
|
+
if self.state.has_waiting_timeout():
|
|
210
|
+
self.state.resume_from_waiting()
|
|
211
|
+
self.state.add_message("assistant", "Waiting timeout reached. Resuming execution.")
|
|
212
|
+
|
|
213
|
+
from strix.cli.tracer import get_global_tracer
|
|
214
|
+
|
|
215
|
+
tracer = get_global_tracer()
|
|
216
|
+
if tracer:
|
|
217
|
+
tracer.update_agent_status(self.state.agent_id, "running")
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
from strix.tools.agents_graph.agents_graph_actions import _agent_graph
|
|
221
|
+
|
|
222
|
+
if self.state.agent_id in _agent_graph["nodes"]:
|
|
223
|
+
_agent_graph["nodes"][self.state.agent_id]["status"] = "running"
|
|
224
|
+
except (ImportError, KeyError):
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
return
|
|
228
|
+
|
|
209
229
|
await asyncio.sleep(0.5)
|
|
210
230
|
|
|
211
231
|
async def _enter_waiting_state(
|
strix/agents/state.py
CHANGED
|
@@ -24,6 +24,7 @@ class AgentState(BaseModel):
|
|
|
24
24
|
stop_requested: bool = False
|
|
25
25
|
waiting_for_input: bool = False
|
|
26
26
|
llm_failed: bool = False
|
|
27
|
+
waiting_start_time: datetime | None = None
|
|
27
28
|
final_result: dict[str, Any] | None = None
|
|
28
29
|
|
|
29
30
|
messages: list[dict[str, Any]] = Field(default_factory=list)
|
|
@@ -88,12 +89,13 @@ class AgentState(BaseModel):
|
|
|
88
89
|
|
|
89
90
|
def enter_waiting_state(self, llm_failed: bool = False) -> None:
|
|
90
91
|
self.waiting_for_input = True
|
|
91
|
-
self.
|
|
92
|
+
self.waiting_start_time = datetime.now(UTC)
|
|
92
93
|
self.llm_failed = llm_failed
|
|
93
94
|
self.last_updated = datetime.now(UTC).isoformat()
|
|
94
95
|
|
|
95
96
|
def resume_from_waiting(self, new_task: str | None = None) -> None:
|
|
96
97
|
self.waiting_for_input = False
|
|
98
|
+
self.waiting_start_time = None
|
|
97
99
|
self.stop_requested = False
|
|
98
100
|
self.completed = False
|
|
99
101
|
self.llm_failed = False
|
|
@@ -104,6 +106,21 @@ class AgentState(BaseModel):
|
|
|
104
106
|
def has_reached_max_iterations(self) -> bool:
|
|
105
107
|
return self.iteration >= self.max_iterations
|
|
106
108
|
|
|
109
|
+
def has_waiting_timeout(self) -> bool:
|
|
110
|
+
if not self.waiting_for_input or not self.waiting_start_time:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
self.stop_requested
|
|
115
|
+
or self.llm_failed
|
|
116
|
+
or self.completed
|
|
117
|
+
or self.has_reached_max_iterations()
|
|
118
|
+
):
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
elapsed = (datetime.now(UTC) - self.waiting_start_time).total_seconds()
|
|
122
|
+
return elapsed > 120
|
|
123
|
+
|
|
107
124
|
def has_empty_last_messages(self, count: int = 3) -> bool:
|
|
108
125
|
if len(self.messages) < count:
|
|
109
126
|
return False
|
strix/cli/app.py
CHANGED
|
@@ -7,9 +7,18 @@ import signal
|
|
|
7
7
|
import sys
|
|
8
8
|
import threading
|
|
9
9
|
from collections.abc import Callable
|
|
10
|
-
from
|
|
10
|
+
from importlib.metadata import PackageNotFoundError
|
|
11
|
+
from importlib.metadata import version as pkg_version
|
|
12
|
+
from typing import TYPE_CHECKING, Any, ClassVar, cast
|
|
11
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from textual.timer import Timer
|
|
16
|
+
|
|
17
|
+
from rich.align import Align
|
|
18
|
+
from rich.console import Group
|
|
12
19
|
from rich.markup import escape as rich_escape
|
|
20
|
+
from rich.panel import Panel
|
|
21
|
+
from rich.style import Style
|
|
13
22
|
from rich.text import Text
|
|
14
23
|
from textual import events, on
|
|
15
24
|
from textual.app import App, ComposeResult
|
|
@@ -26,7 +35,14 @@ from strix.llm.config import LLMConfig
|
|
|
26
35
|
|
|
27
36
|
|
|
28
37
|
def escape_markup(text: str) -> str:
|
|
29
|
-
return rich_escape(text)
|
|
38
|
+
return cast("str", rich_escape(text))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_package_version() -> str:
|
|
42
|
+
try:
|
|
43
|
+
return pkg_version("strix-agent")
|
|
44
|
+
except PackageNotFoundError:
|
|
45
|
+
return "dev"
|
|
30
46
|
|
|
31
47
|
|
|
32
48
|
class ChatTextArea(TextArea): # type: ignore[misc]
|
|
@@ -53,24 +69,85 @@ class ChatTextArea(TextArea): # type: ignore[misc]
|
|
|
53
69
|
|
|
54
70
|
|
|
55
71
|
class SplashScreen(Static): # type: ignore[misc]
|
|
72
|
+
PRIMARY_GREEN = "#22c55e"
|
|
73
|
+
BANNER = (
|
|
74
|
+
" ███████╗████████╗██████╗ ██╗██╗ ██╗\n"
|
|
75
|
+
" ██╔════╝╚══██╔══╝██╔══██╗██║╚██╗██╔╝\n"
|
|
76
|
+
" ███████╗ ██║ ██████╔╝██║ ╚███╔╝\n"
|
|
77
|
+
" ╚════██║ ██║ ██╔══██╗██║ ██╔██╗\n"
|
|
78
|
+
" ███████║ ██║ ██║ ██║██║██╔╝ ██╗\n"
|
|
79
|
+
" ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
83
|
+
super().__init__(*args, **kwargs)
|
|
84
|
+
self._animation_step = 0
|
|
85
|
+
self._animation_timer: Timer | None = None
|
|
86
|
+
self._panel_static: Static | None = None
|
|
87
|
+
self._version = "dev"
|
|
88
|
+
|
|
56
89
|
def compose(self) -> ComposeResult:
|
|
57
|
-
|
|
58
|
-
|
|
90
|
+
self._version = get_package_version()
|
|
91
|
+
self._animation_step = 0
|
|
92
|
+
start_line = self._build_start_line_text(self._animation_step)
|
|
93
|
+
panel = self._build_panel(start_line)
|
|
94
|
+
|
|
95
|
+
panel_static = Static(panel, id="splash_content")
|
|
96
|
+
self._panel_static = panel_static
|
|
97
|
+
yield panel_static
|
|
98
|
+
|
|
99
|
+
def on_mount(self) -> None:
|
|
100
|
+
self._animation_timer = self.set_interval(0.45, self._animate_start_line)
|
|
101
|
+
|
|
102
|
+
def on_unmount(self) -> None:
|
|
103
|
+
if self._animation_timer is not None:
|
|
104
|
+
self._animation_timer.stop()
|
|
105
|
+
self._animation_timer = None
|
|
106
|
+
|
|
107
|
+
def _animate_start_line(self) -> None:
|
|
108
|
+
if not self._panel_static:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
self._animation_step += 1
|
|
112
|
+
start_line = self._build_start_line_text(self._animation_step)
|
|
113
|
+
panel = self._build_panel(start_line)
|
|
114
|
+
self._panel_static.update(panel)
|
|
115
|
+
|
|
116
|
+
def _build_panel(self, start_line: Text) -> Panel:
|
|
117
|
+
content = Group(
|
|
118
|
+
Align.center(Text(self.BANNER.strip("\n"), style=self.PRIMARY_GREEN, justify="center")),
|
|
119
|
+
Align.center(Text(" ")),
|
|
120
|
+
Align.center(self._build_welcome_text()),
|
|
121
|
+
Align.center(self._build_version_text()),
|
|
122
|
+
Align.center(self._build_tagline_text()),
|
|
123
|
+
Align.center(Text(" ")),
|
|
124
|
+
Align.center(start_line.copy()),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return Panel.fit(content, border_style=self.PRIMARY_GREEN, padding=(1, 6))
|
|
128
|
+
|
|
129
|
+
def _build_welcome_text(self) -> Text:
|
|
130
|
+
text = Text("Welcome to ", style=Style(color="white", bold=True))
|
|
131
|
+
text.append("Strix", style=Style(color=self.PRIMARY_GREEN, bold=True))
|
|
132
|
+
text.append("!", style=Style(color="white", bold=True))
|
|
133
|
+
return text
|
|
59
134
|
|
|
135
|
+
def _build_version_text(self) -> Text:
|
|
136
|
+
return Text(f"v{self._version}", style=Style(color="white", dim=True))
|
|
60
137
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
███████╗ ██║ ██████╔╝██║ ╚███╔╝
|
|
64
|
-
╚════██║ ██║ ██╔══██╗██║ ██╔██╗
|
|
65
|
-
███████║ ██║ ██║ ██║██║██╔╝ ██╗
|
|
66
|
-
╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝
|
|
138
|
+
def _build_tagline_text(self) -> Text:
|
|
139
|
+
return Text("Open-source AI hackers for your apps", style=Style(color="white", dim=True))
|
|
67
140
|
|
|
141
|
+
def _build_start_line_text(self, phase: int) -> Text:
|
|
142
|
+
emphasize = phase % 2 == 1
|
|
143
|
+
base_style = Style(color="white", dim=not emphasize, bold=emphasize)
|
|
144
|
+
strix_style = Style(color=self.PRIMARY_GREEN, bold=bool(emphasize))
|
|
68
145
|
|
|
69
|
-
|
|
146
|
+
text = Text("Starting ", style=base_style)
|
|
147
|
+
text.append("Strix", style=strix_style)
|
|
148
|
+
text.append(" Cybersecurity Agent", style=base_style)
|
|
70
149
|
|
|
71
|
-
|
|
72
|
-
"""
|
|
73
|
-
yield Static(ascii_art, id="splash_content")
|
|
150
|
+
return text
|
|
74
151
|
|
|
75
152
|
|
|
76
153
|
class HelpScreen(ModalScreen): # type: ignore[misc]
|
|
@@ -362,7 +439,7 @@ class StrixCLIApp(App): # type: ignore[misc]
|
|
|
362
439
|
def on_mount(self) -> None:
|
|
363
440
|
self.title = "strix"
|
|
364
441
|
|
|
365
|
-
self.set_timer(
|
|
442
|
+
self.set_timer(4.5, self._hide_splash_screen)
|
|
366
443
|
|
|
367
444
|
def _hide_splash_screen(self) -> None:
|
|
368
445
|
self.show_splash = False
|
strix/cli/main.py
CHANGED
|
@@ -40,7 +40,7 @@ def format_token_count(count: float) -> str:
|
|
|
40
40
|
return str(count)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def validate_environment() -> None:
|
|
43
|
+
def validate_environment() -> None: # noqa: PLR0912, PLR0915
|
|
44
44
|
console = Console()
|
|
45
45
|
missing_required_vars = []
|
|
46
46
|
missing_optional_vars = []
|
|
@@ -48,8 +48,23 @@ def validate_environment() -> None:
|
|
|
48
48
|
if not os.getenv("STRIX_LLM"):
|
|
49
49
|
missing_required_vars.append("STRIX_LLM")
|
|
50
50
|
|
|
51
|
+
has_base_url = any(
|
|
52
|
+
[
|
|
53
|
+
os.getenv("LLM_API_BASE"),
|
|
54
|
+
os.getenv("OPENAI_API_BASE"),
|
|
55
|
+
os.getenv("LITELLM_BASE_URL"),
|
|
56
|
+
os.getenv("OLLAMA_API_BASE"),
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
|
|
51
60
|
if not os.getenv("LLM_API_KEY"):
|
|
52
|
-
|
|
61
|
+
if not has_base_url:
|
|
62
|
+
missing_required_vars.append("LLM_API_KEY")
|
|
63
|
+
else:
|
|
64
|
+
missing_optional_vars.append("LLM_API_KEY")
|
|
65
|
+
|
|
66
|
+
if not has_base_url:
|
|
67
|
+
missing_optional_vars.append("LLM_API_BASE")
|
|
53
68
|
|
|
54
69
|
if not os.getenv("PERPLEXITY_API_KEY"):
|
|
55
70
|
missing_optional_vars.append("PERPLEXITY_API_KEY")
|
|
@@ -65,40 +80,73 @@ def validate_environment() -> None:
|
|
|
65
80
|
error_text.append(" is not set\n", style="white")
|
|
66
81
|
|
|
67
82
|
if missing_optional_vars:
|
|
68
|
-
error_text.append(
|
|
69
|
-
"\nOptional (but recommended) environment variables:\n", style="dim white"
|
|
70
|
-
)
|
|
83
|
+
error_text.append("\nOptional environment variables:\n", style="dim white")
|
|
71
84
|
for var in missing_optional_vars:
|
|
72
85
|
error_text.append(f"• {var}", style="dim yellow")
|
|
73
86
|
error_text.append(" is not set\n", style="dim white")
|
|
74
87
|
|
|
75
88
|
error_text.append("\nRequired environment variables:\n", style="white")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
for var in missing_required_vars:
|
|
90
|
+
if var == "STRIX_LLM":
|
|
91
|
+
error_text.append("• ", style="white")
|
|
92
|
+
error_text.append("STRIX_LLM", style="bold cyan")
|
|
93
|
+
error_text.append(
|
|
94
|
+
" - Model name to use with litellm (e.g., 'openai/gpt-5')\n",
|
|
95
|
+
style="white",
|
|
96
|
+
)
|
|
97
|
+
elif var == "LLM_API_KEY":
|
|
98
|
+
error_text.append("• ", style="white")
|
|
99
|
+
error_text.append("LLM_API_KEY", style="bold cyan")
|
|
100
|
+
error_text.append(
|
|
101
|
+
" - API key for the LLM provider (required for cloud providers)\n",
|
|
102
|
+
style="white",
|
|
103
|
+
)
|
|
85
104
|
|
|
86
105
|
if missing_optional_vars:
|
|
87
106
|
error_text.append("\nOptional environment variables:\n", style="white")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
for var in missing_optional_vars:
|
|
108
|
+
if var == "LLM_API_KEY":
|
|
109
|
+
error_text.append("• ", style="white")
|
|
110
|
+
error_text.append("LLM_API_KEY", style="bold cyan")
|
|
111
|
+
error_text.append(" - API key for the LLM provider\n", style="white")
|
|
112
|
+
elif var == "LLM_API_BASE":
|
|
113
|
+
error_text.append("• ", style="white")
|
|
114
|
+
error_text.append("LLM_API_BASE", style="bold cyan")
|
|
115
|
+
error_text.append(
|
|
116
|
+
" - Custom API base URL if using local models (e.g., Ollama, LMStudio)\n",
|
|
117
|
+
style="white",
|
|
118
|
+
)
|
|
119
|
+
elif var == "PERPLEXITY_API_KEY":
|
|
120
|
+
error_text.append("• ", style="white")
|
|
121
|
+
error_text.append("PERPLEXITY_API_KEY", style="bold cyan")
|
|
122
|
+
error_text.append(
|
|
123
|
+
" - API key for Perplexity AI web search (enables real-time research)\n",
|
|
124
|
+
style="white",
|
|
125
|
+
)
|
|
94
126
|
|
|
95
127
|
error_text.append("\nExample setup:\n", style="white")
|
|
96
128
|
error_text.append("export STRIX_LLM='openai/gpt-5'\n", style="dim white")
|
|
97
|
-
|
|
129
|
+
|
|
130
|
+
if "LLM_API_KEY" in missing_required_vars:
|
|
131
|
+
error_text.append("export LLM_API_KEY='your-api-key-here'\n", style="dim white")
|
|
132
|
+
|
|
98
133
|
if missing_optional_vars:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
for var in missing_optional_vars:
|
|
135
|
+
if var == "LLM_API_KEY":
|
|
136
|
+
error_text.append(
|
|
137
|
+
"export LLM_API_KEY='your-api-key-here' # optional with local models\n",
|
|
138
|
+
style="dim white",
|
|
139
|
+
)
|
|
140
|
+
elif var == "LLM_API_BASE":
|
|
141
|
+
error_text.append(
|
|
142
|
+
"export LLM_API_BASE='http://localhost:11434' "
|
|
143
|
+
"# needed for local models only\n",
|
|
144
|
+
style="dim white",
|
|
145
|
+
)
|
|
146
|
+
elif var == "PERPLEXITY_API_KEY":
|
|
147
|
+
error_text.append(
|
|
148
|
+
"export PERPLEXITY_API_KEY='your-perplexity-key-here'\n", style="dim white"
|
|
149
|
+
)
|
|
102
150
|
|
|
103
151
|
panel = Panel(
|
|
104
152
|
error_text,
|
|
@@ -152,6 +200,15 @@ async def warm_up_llm() -> None:
|
|
|
152
200
|
if api_key:
|
|
153
201
|
litellm.api_key = api_key
|
|
154
202
|
|
|
203
|
+
api_base = (
|
|
204
|
+
os.getenv("LLM_API_BASE")
|
|
205
|
+
or os.getenv("OPENAI_API_BASE")
|
|
206
|
+
or os.getenv("LITELLM_BASE_URL")
|
|
207
|
+
or os.getenv("OLLAMA_API_BASE")
|
|
208
|
+
)
|
|
209
|
+
if api_base:
|
|
210
|
+
litellm.api_base = api_base
|
|
211
|
+
|
|
155
212
|
test_messages = [
|
|
156
213
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
157
214
|
{"role": "user", "content": "Reply with just 'OK'."},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any, ClassVar
|
|
2
|
+
from typing import Any, ClassVar, cast
|
|
3
3
|
|
|
4
4
|
from rich.markup import escape as rich_escape
|
|
5
5
|
from textual.widgets import Static
|
|
@@ -17,7 +17,7 @@ class BaseToolRenderer(ABC):
|
|
|
17
17
|
|
|
18
18
|
@classmethod
|
|
19
19
|
def escape_markup(cls, text: str) -> str:
|
|
20
|
-
return rich_escape(text)
|
|
20
|
+
return cast("str", rich_escape(text))
|
|
21
21
|
|
|
22
22
|
@classmethod
|
|
23
23
|
def format_args(cls, args: dict[str, Any], max_length: int = 500) -> str:
|
|
@@ -27,7 +27,8 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
|
|
27
27
|
if severity:
|
|
28
28
|
severity_color = cls._get_severity_color(severity.lower())
|
|
29
29
|
content_parts.append(
|
|
30
|
-
f" [dim]Severity: [{severity_color}]
|
|
30
|
+
f" [dim]Severity: [{severity_color}]"
|
|
31
|
+
f"{cls.escape_markup(severity.upper())}[/{severity_color}][/]"
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
if content:
|
strix/llm/llm.py
CHANGED
|
@@ -28,6 +28,15 @@ api_key = os.getenv("LLM_API_KEY")
|
|
|
28
28
|
if api_key:
|
|
29
29
|
litellm.api_key = api_key
|
|
30
30
|
|
|
31
|
+
api_base = (
|
|
32
|
+
os.getenv("LLM_API_BASE")
|
|
33
|
+
or os.getenv("OPENAI_API_BASE")
|
|
34
|
+
or os.getenv("LITELLM_BASE_URL")
|
|
35
|
+
or os.getenv("OLLAMA_API_BASE")
|
|
36
|
+
)
|
|
37
|
+
if api_base:
|
|
38
|
+
litellm.api_base = api_base
|
|
39
|
+
|
|
31
40
|
|
|
32
41
|
class LLMRequestFailedError(Exception):
|
|
33
42
|
def __init__(self, message: str, details: str | None = None):
|
strix/prompts/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# 📚 Strix Prompt Modules
|
|
2
|
+
|
|
3
|
+
## 🎯 Overview
|
|
4
|
+
|
|
5
|
+
Prompt modules are specialized knowledge packages that enhance Strix agents with deep expertise in specific vulnerability types, technologies, and testing methodologies. Each module provides advanced techniques, practical examples, and validation methods that go beyond baseline security knowledge.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🏗️ Architecture
|
|
10
|
+
|
|
11
|
+
### How Prompts Work
|
|
12
|
+
|
|
13
|
+
When an agent is created, it can load up to 5 specialized prompt modules relevant to the specific subtask and context at hand:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# Agent creation with specialized modules
|
|
17
|
+
create_agent(
|
|
18
|
+
task="Test authentication mechanisms in API",
|
|
19
|
+
name="Auth Specialist",
|
|
20
|
+
prompt_modules="authentication_jwt,business_logic"
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The modules are dynamically injected into the agent's system prompt, allowing it to operate with deep expertise tailored to the specific vulnerability types or technologies required for the task at hand.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 📁 Module Categories
|
|
29
|
+
|
|
30
|
+
| Category | Purpose |
|
|
31
|
+
|----------|---------|
|
|
32
|
+
| **`/vulnerabilities`** | Advanced testing techniques for core vulnerability classes like authentication bypasses, business logic flaws, and race conditions |
|
|
33
|
+
| **`/frameworks`** | Specific testing methods for popular frameworks e.g. Django, Express, FastAPI, and Next.js |
|
|
34
|
+
| **`/technologies`** | Specialized techniques for third-party services such as Supabase, Firebase, Auth0, and payment gateways |
|
|
35
|
+
| **`/protocols`** | Protocol-specific testing patterns for GraphQL, WebSocket, OAuth, and other communication standards |
|
|
36
|
+
| **`/cloud`** | Cloud provider security testing for AWS, Azure, GCP, and Kubernetes environments |
|
|
37
|
+
| **`/reconnaissance`** | Advanced information gathering and enumeration techniques for comprehensive attack surface mapping |
|
|
38
|
+
| **`/custom`** | Community-contributed modules for specialized or industry-specific testing scenarios |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🎨 Creating New Modules
|
|
43
|
+
|
|
44
|
+
### What Should a Module Contain?
|
|
45
|
+
|
|
46
|
+
A good prompt module is a structured knowledge package that typically includes:
|
|
47
|
+
|
|
48
|
+
- **Advanced techniques** - Non-obvious methods specific to the task and domain
|
|
49
|
+
- **Practical examples** - Working payloads, commands, or test cases with variations
|
|
50
|
+
- **Validation methods** - How to confirm findings and avoid false positives
|
|
51
|
+
- **Context-specific insights** - Environment and version nuances, configuration-dependent behavior, and edge cases
|
|
52
|
+
|
|
53
|
+
Modules use XML-style tags for structure and focus on deep, specialized knowledge that significantly enhances agent capabilities for that specific context.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🤝 Contributing
|
|
58
|
+
|
|
59
|
+
Community contributions are more than welcome — contribute new modules via [pull requests](https://github.com/usestrix/strix/pulls) or [GitHub issues](https://github.com/usestrix/strix/issues) to help expand the collection and improve extensibility for Strix agents.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
> [!NOTE]
|
|
64
|
+
> **Work in Progress** - We're actively expanding the prompt module collection with specialized techniques and new categories.
|
strix/prompts/__init__.py
CHANGED
|
@@ -58,7 +58,7 @@ def generate_modules_description() -> str:
|
|
|
58
58
|
modules_str = ", ".join(sorted_modules)
|
|
59
59
|
|
|
60
60
|
description = (
|
|
61
|
-
f"List of prompt modules to load for this agent (max
|
|
61
|
+
f"List of prompt modules to load for this agent (max 5). Available modules: {modules_str}. "
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
example_modules = sorted_modules[:2]
|
|
File without changes
|
|
File without changes
|