strix-agent 0.1.1__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.
Files changed (99) hide show
  1. strix/__init__.py +0 -0
  2. strix/agents/StrixAgent/__init__.py +4 -0
  3. strix/agents/StrixAgent/strix_agent.py +60 -0
  4. strix/agents/StrixAgent/system_prompt.jinja +504 -0
  5. strix/agents/__init__.py +10 -0
  6. strix/agents/base_agent.py +394 -0
  7. strix/agents/state.py +139 -0
  8. strix/cli/__init__.py +4 -0
  9. strix/cli/app.py +1124 -0
  10. strix/cli/assets/cli.tcss +680 -0
  11. strix/cli/main.py +542 -0
  12. strix/cli/tool_components/__init__.py +39 -0
  13. strix/cli/tool_components/agents_graph_renderer.py +129 -0
  14. strix/cli/tool_components/base_renderer.py +61 -0
  15. strix/cli/tool_components/browser_renderer.py +107 -0
  16. strix/cli/tool_components/file_edit_renderer.py +95 -0
  17. strix/cli/tool_components/finish_renderer.py +32 -0
  18. strix/cli/tool_components/notes_renderer.py +108 -0
  19. strix/cli/tool_components/proxy_renderer.py +255 -0
  20. strix/cli/tool_components/python_renderer.py +34 -0
  21. strix/cli/tool_components/registry.py +72 -0
  22. strix/cli/tool_components/reporting_renderer.py +53 -0
  23. strix/cli/tool_components/scan_info_renderer.py +58 -0
  24. strix/cli/tool_components/terminal_renderer.py +99 -0
  25. strix/cli/tool_components/thinking_renderer.py +29 -0
  26. strix/cli/tool_components/user_message_renderer.py +43 -0
  27. strix/cli/tool_components/web_search_renderer.py +28 -0
  28. strix/cli/tracer.py +308 -0
  29. strix/llm/__init__.py +14 -0
  30. strix/llm/config.py +19 -0
  31. strix/llm/llm.py +310 -0
  32. strix/llm/memory_compressor.py +206 -0
  33. strix/llm/request_queue.py +63 -0
  34. strix/llm/utils.py +84 -0
  35. strix/prompts/__init__.py +113 -0
  36. strix/prompts/coordination/root_agent.jinja +41 -0
  37. strix/prompts/vulnerabilities/authentication_jwt.jinja +129 -0
  38. strix/prompts/vulnerabilities/business_logic.jinja +143 -0
  39. strix/prompts/vulnerabilities/csrf.jinja +168 -0
  40. strix/prompts/vulnerabilities/idor.jinja +164 -0
  41. strix/prompts/vulnerabilities/race_conditions.jinja +194 -0
  42. strix/prompts/vulnerabilities/rce.jinja +222 -0
  43. strix/prompts/vulnerabilities/sql_injection.jinja +216 -0
  44. strix/prompts/vulnerabilities/ssrf.jinja +168 -0
  45. strix/prompts/vulnerabilities/xss.jinja +221 -0
  46. strix/prompts/vulnerabilities/xxe.jinja +276 -0
  47. strix/runtime/__init__.py +19 -0
  48. strix/runtime/docker_runtime.py +298 -0
  49. strix/runtime/runtime.py +25 -0
  50. strix/runtime/tool_server.py +97 -0
  51. strix/tools/__init__.py +64 -0
  52. strix/tools/agents_graph/__init__.py +16 -0
  53. strix/tools/agents_graph/agents_graph_actions.py +610 -0
  54. strix/tools/agents_graph/agents_graph_actions_schema.xml +223 -0
  55. strix/tools/argument_parser.py +120 -0
  56. strix/tools/browser/__init__.py +4 -0
  57. strix/tools/browser/browser_actions.py +236 -0
  58. strix/tools/browser/browser_actions_schema.xml +183 -0
  59. strix/tools/browser/browser_instance.py +533 -0
  60. strix/tools/browser/tab_manager.py +342 -0
  61. strix/tools/executor.py +302 -0
  62. strix/tools/file_edit/__init__.py +4 -0
  63. strix/tools/file_edit/file_edit_actions.py +141 -0
  64. strix/tools/file_edit/file_edit_actions_schema.xml +128 -0
  65. strix/tools/finish/__init__.py +4 -0
  66. strix/tools/finish/finish_actions.py +167 -0
  67. strix/tools/finish/finish_actions_schema.xml +45 -0
  68. strix/tools/notes/__init__.py +14 -0
  69. strix/tools/notes/notes_actions.py +191 -0
  70. strix/tools/notes/notes_actions_schema.xml +150 -0
  71. strix/tools/proxy/__init__.py +20 -0
  72. strix/tools/proxy/proxy_actions.py +101 -0
  73. strix/tools/proxy/proxy_actions_schema.xml +267 -0
  74. strix/tools/proxy/proxy_manager.py +785 -0
  75. strix/tools/python/__init__.py +4 -0
  76. strix/tools/python/python_actions.py +47 -0
  77. strix/tools/python/python_actions_schema.xml +131 -0
  78. strix/tools/python/python_instance.py +172 -0
  79. strix/tools/python/python_manager.py +131 -0
  80. strix/tools/registry.py +196 -0
  81. strix/tools/reporting/__init__.py +6 -0
  82. strix/tools/reporting/reporting_actions.py +63 -0
  83. strix/tools/reporting/reporting_actions_schema.xml +30 -0
  84. strix/tools/terminal/__init__.py +4 -0
  85. strix/tools/terminal/terminal_actions.py +53 -0
  86. strix/tools/terminal/terminal_actions_schema.xml +114 -0
  87. strix/tools/terminal/terminal_instance.py +231 -0
  88. strix/tools/terminal/terminal_manager.py +191 -0
  89. strix/tools/thinking/__init__.py +4 -0
  90. strix/tools/thinking/thinking_actions.py +18 -0
  91. strix/tools/thinking/thinking_actions_schema.xml +52 -0
  92. strix/tools/web_search/__init__.py +4 -0
  93. strix/tools/web_search/web_search_actions.py +80 -0
  94. strix/tools/web_search/web_search_actions_schema.xml +83 -0
  95. strix_agent-0.1.1.dist-info/LICENSE +201 -0
  96. strix_agent-0.1.1.dist-info/METADATA +200 -0
  97. strix_agent-0.1.1.dist-info/RECORD +99 -0
  98. strix_agent-0.1.1.dist-info/WHEEL +4 -0
  99. strix_agent-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,206 @@
1
+ import logging
2
+ import os
3
+ from typing import Any
4
+
5
+ import litellm
6
+
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ MAX_TOTAL_TOKENS = 100_000
12
+ MIN_RECENT_MESSAGES = 15
13
+
14
+ SUMMARY_PROMPT_TEMPLATE = """You are an agent performing context
15
+ condensation for a security agent. Your job is to compress scan data while preserving
16
+ ALL operationally critical information for continuing the security assessment.
17
+
18
+ CRITICAL ELEMENTS TO PRESERVE:
19
+ - Discovered vulnerabilities and potential attack vectors
20
+ - Scan results and tool outputs (compressed but maintaining key findings)
21
+ - Access credentials, tokens, or authentication details found
22
+ - System architecture insights and potential weak points
23
+ - Progress made in the assessment
24
+ - Failed attempts and dead ends (to avoid duplication)
25
+ - Any decisions made about the testing approach
26
+
27
+ COMPRESSION GUIDELINES:
28
+ - Preserve exact technical details (URLs, paths, parameters, payloads)
29
+ - Summarize verbose tool outputs while keeping critical findings
30
+ - Maintain version numbers, specific technologies identified
31
+ - Keep exact error messages that might indicate vulnerabilities
32
+ - Compress repetitive or similar findings into consolidated form
33
+
34
+ Remember: Another security agent will use this summary to continue the assessment.
35
+ They must be able to pick up exactly where you left off without losing any
36
+ operational advantage or context needed to find vulnerabilities.
37
+
38
+ CONVERSATION SEGMENT TO SUMMARIZE:
39
+ {conversation}
40
+
41
+ Provide a technically precise summary that preserves all operational security context while
42
+ keeping the summary concise and to the point."""
43
+
44
+
45
+ def _count_tokens(text: str, model: str) -> int:
46
+ try:
47
+ count = litellm.token_counter(model=model, text=text)
48
+ return int(count)
49
+ except Exception:
50
+ logger.exception("Failed to count tokens")
51
+ return len(text) // 4 # Rough estimate
52
+
53
+
54
+ def _get_message_tokens(msg: dict[str, Any], model: str) -> int:
55
+ content = msg.get("content", "")
56
+ if isinstance(content, str):
57
+ return _count_tokens(content, model)
58
+ if isinstance(content, list):
59
+ return sum(
60
+ _count_tokens(item.get("text", ""), model)
61
+ for item in content
62
+ if isinstance(item, dict) and item.get("type") == "text"
63
+ )
64
+ return 0
65
+
66
+
67
+ def _extract_message_text(msg: dict[str, Any]) -> str:
68
+ content = msg.get("content", "")
69
+ if isinstance(content, str):
70
+ return content
71
+
72
+ if isinstance(content, list):
73
+ parts = []
74
+ for item in content:
75
+ if isinstance(item, dict):
76
+ if item.get("type") == "text":
77
+ parts.append(item.get("text", ""))
78
+ elif item.get("type") == "image_url":
79
+ parts.append("[IMAGE]")
80
+ return " ".join(parts)
81
+
82
+ return str(content)
83
+
84
+
85
+ def _summarize_messages(
86
+ messages: list[dict[str, Any]],
87
+ model: str,
88
+ ) -> dict[str, Any]:
89
+ if not messages:
90
+ empty_summary = "<context_summary message_count='0'>{text}</context_summary>"
91
+ return {
92
+ "role": "assistant",
93
+ "content": empty_summary.format(text="No messages to summarize"),
94
+ }
95
+
96
+ formatted = []
97
+ for msg in messages:
98
+ role = msg.get("role", "unknown")
99
+ text = _extract_message_text(msg)
100
+ formatted.append(f"{role}: {text}")
101
+
102
+ conversation = "\n".join(formatted)
103
+ prompt = SUMMARY_PROMPT_TEMPLATE.format(conversation=conversation)
104
+
105
+ try:
106
+ completion_args = {
107
+ "model": model,
108
+ "messages": [{"role": "user", "content": prompt}],
109
+ }
110
+
111
+ response = litellm.completion(**completion_args)
112
+ summary = response.choices[0].message.content
113
+ summary_msg = "<context_summary message_count='{count}'>{text}</context_summary>"
114
+ return {
115
+ "role": "assistant",
116
+ "content": summary_msg.format(count=len(messages), text=summary),
117
+ }
118
+ except Exception:
119
+ logger.exception("Failed to summarize messages")
120
+ return messages[0]
121
+
122
+
123
+ def _handle_images(messages: list[dict[str, Any]], max_images: int) -> None:
124
+ image_count = 0
125
+ for msg in reversed(messages):
126
+ content = msg.get("content", [])
127
+ if isinstance(content, list):
128
+ for item in content:
129
+ if isinstance(item, dict) and item.get("type") == "image_url":
130
+ if image_count >= max_images:
131
+ item.update(
132
+ {
133
+ "type": "text",
134
+ "text": "[Previously attached image removed to preserve context]",
135
+ }
136
+ )
137
+ else:
138
+ image_count += 1
139
+
140
+
141
+ class MemoryCompressor:
142
+ def __init__(
143
+ self,
144
+ max_images: int = 3,
145
+ model_name: str | None = None,
146
+ ):
147
+ self.max_images = max_images
148
+ self.model_name = model_name or os.getenv("STRIX_LLM", "anthropic/claude-sonnet-4-20250514")
149
+
150
+ if not self.model_name:
151
+ raise ValueError("STRIX_LLM environment variable must be set and not empty")
152
+
153
+ def compress_history(
154
+ self,
155
+ messages: list[dict[str, Any]],
156
+ ) -> list[dict[str, Any]]:
157
+ """Compress conversation history to stay within token limits.
158
+
159
+ Strategy:
160
+ 1. Handle image limits first
161
+ 2. Keep all system messages
162
+ 3. Keep minimum recent messages
163
+ 4. Summarize older messages when total tokens exceed limit
164
+
165
+ The compression preserves:
166
+ - All system messages unchanged
167
+ - Most recent messages intact
168
+ - Critical security context in summaries
169
+ - Recent images for visual context
170
+ - Technical details and findings
171
+ """
172
+ if not messages:
173
+ return messages
174
+
175
+ _handle_images(messages, self.max_images)
176
+
177
+ system_msgs = []
178
+ regular_msgs = []
179
+ for msg in messages:
180
+ if msg.get("role") == "system":
181
+ system_msgs.append(msg)
182
+ else:
183
+ regular_msgs.append(msg)
184
+
185
+ recent_msgs = regular_msgs[-MIN_RECENT_MESSAGES:]
186
+ old_msgs = regular_msgs[:-MIN_RECENT_MESSAGES]
187
+
188
+ # Type assertion since we ensure model_name is not None in __init__
189
+ model_name: str = self.model_name # type: ignore[assignment]
190
+
191
+ total_tokens = sum(
192
+ _get_message_tokens(msg, model_name) for msg in system_msgs + regular_msgs
193
+ )
194
+
195
+ if total_tokens <= MAX_TOTAL_TOKENS * 0.9:
196
+ return messages
197
+
198
+ compressed = []
199
+ chunk_size = 10
200
+ for i in range(0, len(old_msgs), chunk_size):
201
+ chunk = old_msgs[i : i + chunk_size]
202
+ summary = _summarize_messages(chunk, model_name)
203
+ if summary:
204
+ compressed.append(summary)
205
+
206
+ return system_msgs + compressed + recent_msgs
@@ -0,0 +1,63 @@
1
+ import asyncio
2
+ import logging
3
+ import threading
4
+ import time
5
+ from typing import Any
6
+
7
+ from litellm import ModelResponse, completion
8
+ from tenacity import retry, stop_after_attempt, wait_exponential
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class LLMRequestQueue:
15
+ def __init__(self, max_concurrent: int = 6, delay_between_requests: float = 1.0):
16
+ self.max_concurrent = max_concurrent
17
+ self.delay_between_requests = delay_between_requests
18
+ self._semaphore = threading.BoundedSemaphore(max_concurrent)
19
+ self._last_request_time = 0.0
20
+ self._lock = threading.Lock()
21
+
22
+ async def make_request(self, completion_args: dict[str, Any]) -> ModelResponse:
23
+ try:
24
+ while not self._semaphore.acquire(timeout=0.2):
25
+ await asyncio.sleep(0.1)
26
+
27
+ with self._lock:
28
+ now = time.time()
29
+ time_since_last = now - self._last_request_time
30
+ sleep_needed = max(0, self.delay_between_requests - time_since_last)
31
+ self._last_request_time = now + sleep_needed
32
+
33
+ if sleep_needed > 0:
34
+ await asyncio.sleep(sleep_needed)
35
+
36
+ return await self._reliable_request(completion_args)
37
+ finally:
38
+ self._semaphore.release()
39
+
40
+ @retry( # type: ignore[misc]
41
+ stop=stop_after_attempt(15),
42
+ wait=wait_exponential(multiplier=1.2, min=1, max=300),
43
+ reraise=True,
44
+ )
45
+ async def _reliable_request(self, completion_args: dict[str, Any]) -> ModelResponse:
46
+ response = completion(**completion_args, stream=False)
47
+ if isinstance(response, ModelResponse):
48
+ return response
49
+ self._raise_unexpected_response()
50
+ raise RuntimeError("Unreachable code")
51
+
52
+ def _raise_unexpected_response(self) -> None:
53
+ raise RuntimeError("Unexpected response type")
54
+
55
+
56
+ _global_queue: LLMRequestQueue | None = None
57
+
58
+
59
+ def get_global_queue() -> LLMRequestQueue:
60
+ global _global_queue # noqa: PLW0603
61
+ if _global_queue is None:
62
+ _global_queue = LLMRequestQueue()
63
+ return _global_queue
strix/llm/utils.py ADDED
@@ -0,0 +1,84 @@
1
+ import re
2
+ from typing import Any
3
+
4
+
5
+ def _truncate_to_first_function(content: str) -> str:
6
+ if not content:
7
+ return content
8
+
9
+ function_starts = [match.start() for match in re.finditer(r"<function=", content)]
10
+
11
+ if len(function_starts) >= 2:
12
+ second_function_start = function_starts[1]
13
+
14
+ return content[:second_function_start].rstrip()
15
+
16
+ return content
17
+
18
+
19
+ def parse_tool_invocations(content: str) -> list[dict[str, Any]] | None:
20
+ content = _fix_stopword(content)
21
+
22
+ tool_invocations: list[dict[str, Any]] = []
23
+
24
+ fn_regex_pattern = r"<function=([^>]+)>\n?(.*?)</function>"
25
+ fn_param_regex_pattern = r"<parameter=([^>]+)>(.*?)</parameter>"
26
+
27
+ fn_matches = re.finditer(fn_regex_pattern, content, re.DOTALL)
28
+
29
+ for fn_match in fn_matches:
30
+ fn_name = fn_match.group(1)
31
+ fn_body = fn_match.group(2)
32
+
33
+ param_matches = re.finditer(fn_param_regex_pattern, fn_body, re.DOTALL)
34
+
35
+ args = {}
36
+ for param_match in param_matches:
37
+ param_name = param_match.group(1)
38
+ param_value = param_match.group(2).strip()
39
+ args[param_name] = param_value
40
+
41
+ tool_invocations.append({"toolName": fn_name, "args": args})
42
+
43
+ return tool_invocations if tool_invocations else None
44
+
45
+
46
+ def _fix_stopword(content: str) -> str:
47
+ if "<function=" in content and content.count("<function=") == 1:
48
+ if content.endswith("</"):
49
+ content = content.rstrip() + "function>"
50
+ elif not content.rstrip().endswith("</function>"):
51
+ content = content + "\n</function>"
52
+ return content
53
+
54
+
55
+ def format_tool_call(tool_name: str, args: dict[str, Any]) -> str:
56
+ xml_parts = [f"<function={tool_name}>"]
57
+
58
+ for key, value in args.items():
59
+ xml_parts.append(f"<parameter={key}>{value}</parameter>")
60
+
61
+ xml_parts.append("</function>")
62
+
63
+ return "\n".join(xml_parts)
64
+
65
+
66
+ def clean_content(content: str) -> str:
67
+ if not content:
68
+ return ""
69
+
70
+ content = _fix_stopword(content)
71
+
72
+ tool_pattern = r"<function=[^>]+>.*?</function>"
73
+ cleaned = re.sub(tool_pattern, "", content, flags=re.DOTALL)
74
+
75
+ hidden_xml_patterns = [
76
+ r"<inter_agent_message>.*?</inter_agent_message>",
77
+ r"<agent_completion_report>.*?</agent_completion_report>",
78
+ ]
79
+ for pattern in hidden_xml_patterns:
80
+ cleaned = re.sub(pattern, "", cleaned, flags=re.DOTALL | re.IGNORECASE)
81
+
82
+ cleaned = re.sub(r"\n\s*\n", "\n\n", cleaned)
83
+
84
+ return cleaned.strip()
@@ -0,0 +1,113 @@
1
+ from pathlib import Path
2
+
3
+ from jinja2 import Environment
4
+
5
+
6
+ def get_available_prompt_modules() -> dict[str, list[str]]:
7
+ modules_dir = Path(__file__).parent
8
+ available_modules = {}
9
+
10
+ for category_dir in modules_dir.iterdir():
11
+ if category_dir.is_dir() and not category_dir.name.startswith("__"):
12
+ category_name = category_dir.name
13
+ modules = []
14
+
15
+ for file_path in category_dir.glob("*.jinja"):
16
+ module_name = file_path.stem
17
+ modules.append(module_name)
18
+
19
+ if modules:
20
+ available_modules[category_name] = sorted(modules)
21
+
22
+ return available_modules
23
+
24
+
25
+ def get_all_module_names() -> set[str]:
26
+ all_modules = set()
27
+ for category_modules in get_available_prompt_modules().values():
28
+ all_modules.update(category_modules)
29
+ return all_modules
30
+
31
+
32
+ def validate_module_names(module_names: list[str]) -> dict[str, list[str]]:
33
+ available_modules = get_all_module_names()
34
+ valid_modules = []
35
+ invalid_modules = []
36
+
37
+ for module_name in module_names:
38
+ if module_name in available_modules:
39
+ valid_modules.append(module_name)
40
+ else:
41
+ invalid_modules.append(module_name)
42
+
43
+ return {"valid": valid_modules, "invalid": invalid_modules}
44
+
45
+
46
+ def generate_modules_description() -> str:
47
+ available_modules = get_available_prompt_modules()
48
+
49
+ if not available_modules:
50
+ return "No prompt modules available"
51
+
52
+ description_parts = []
53
+
54
+ for category, modules in available_modules.items():
55
+ modules_str = ", ".join(modules)
56
+ description_parts.append(f"{category} ({modules_str})")
57
+
58
+ description = (
59
+ f"List of prompt modules to load for this agent (max 3). "
60
+ f"Available modules: {', '.join(description_parts)}. "
61
+ )
62
+
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
+
69
+ if example_modules:
70
+ example = f"Example: {example_modules[:2]} for specialized agent"
71
+ description += example
72
+
73
+ return description
74
+
75
+
76
+ def load_prompt_modules(module_names: list[str], jinja_env: Environment) -> dict[str, str]:
77
+ import logging
78
+
79
+ logger = logging.getLogger(__name__)
80
+ module_content = {}
81
+ prompts_dir = Path(__file__).parent
82
+
83
+ available_modules = get_available_prompt_modules()
84
+
85
+ for module_name in module_names:
86
+ try:
87
+ module_path = None
88
+
89
+ if "/" in module_name:
90
+ module_path = f"{module_name}.jinja"
91
+ else:
92
+ for category, modules in available_modules.items():
93
+ if module_name in modules:
94
+ module_path = f"{category}/{module_name}.jinja"
95
+ break
96
+
97
+ if not module_path:
98
+ root_candidate = f"{module_name}.jinja"
99
+ if (prompts_dir / root_candidate).exists():
100
+ module_path = root_candidate
101
+
102
+ if module_path and (prompts_dir / module_path).exists():
103
+ template = jinja_env.get_template(module_path)
104
+ var_name = module_name.split("/")[-1]
105
+ module_content[var_name] = template.render()
106
+ logger.info(f"Loaded prompt module: {module_name} -> {var_name}")
107
+ else:
108
+ logger.warning(f"Prompt module not found: {module_name}")
109
+
110
+ except (FileNotFoundError, OSError, ValueError) as e:
111
+ logger.warning(f"Failed to load prompt module {module_name}: {e}")
112
+
113
+ return module_content
@@ -0,0 +1,41 @@
1
+ <coordination_role>
2
+ You are a COORDINATION AGENT ONLY. You do NOT perform any security testing, vulnerability assessment, or technical work yourself.
3
+
4
+ Your ONLY responsibilities:
5
+ 1. Create specialized agents for specific security tasks
6
+ 2. Monitor agent progress and coordinate between them
7
+ 3. Compile final scan reports from agent findings
8
+ 4. Manage agent communication and dependencies
9
+
10
+ CRITICAL RESTRICTIONS:
11
+ - NEVER perform vulnerability testing or security assessments
12
+ - NEVER write detailed vulnerability reports (only compile final summaries)
13
+ - ONLY use agent_graph and finish tools for coordination
14
+ - You can create agents throughout the scan process, depending on the task and findings, not just at the beginning!
15
+ </coordination_role>
16
+
17
+ <agent_management>
18
+ BEFORE CREATING AGENTS:
19
+ 1. Analyze the target scope and break into independent tasks
20
+ 2. Check existing agents to avoid duplication
21
+ 3. Create agents with clear, specific objectives to avoid duplication
22
+
23
+ AGENT TYPES YOU CAN CREATE:
24
+ - Reconnaissance: subdomain enum, port scanning, tech identification, etc.
25
+ - Vulnerability Testing: SQL injection, XSS, auth bypass, IDOR, RCE, SSRF, etc. Can be black-box or white-box.
26
+ - Direct vulnerability testing agents to implement hierarchical workflow (per finding: discover, verify, report, fix): each one should create validation agents for findings verification, which spawn reporting agents for documentation, which create fix agents for remediation
27
+
28
+ COORDINATION GUIDELINES:
29
+ - Ensure clear task boundaries and success criteria
30
+ - Terminate redundant agents when objectives overlap
31
+ - Use message passing for agent communication
32
+ </agent_management>
33
+
34
+ <final_responsibilities>
35
+ When all agents complete:
36
+ 1. Collect findings from all agents
37
+ 2. Compile a final scan summary report
38
+ 3. Use finish tool to complete the assessment
39
+
40
+ Your value is in orchestration, not execution.
41
+ </final_responsibilities>
@@ -0,0 +1,129 @@
1
+ <authentication_jwt_guide>
2
+ <title>AUTHENTICATION & JWT VULNERABILITIES</title>
3
+
4
+ <critical>Authentication flaws lead to complete account takeover. JWT misconfigurations are everywhere.</critical>
5
+
6
+ <jwt_structure>
7
+ header.payload.signature
8
+ - Header: {"alg":"HS256","typ":"JWT"}
9
+ - Payload: {"sub":"1234","name":"John","iat":1516239022}
10
+ - Signature: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
11
+ </jwt_structure>
12
+
13
+ <common_attacks>
14
+ <algorithm_confusion>
15
+ RS256 to HS256:
16
+ - Change RS256 to HS256 in header
17
+ - Use public key as HMAC secret
18
+ - Sign token with public key (often in /jwks.json or /.well-known/)
19
+ </algorithm_confusion>
20
+
21
+ <none_algorithm>
22
+ - Set "alg": "none" in header
23
+ - Remove signature completely (keep the trailing dot)
24
+ </none_algorithm>
25
+
26
+ <weak_secrets>
27
+ Common secrets: 'secret', 'password', '123456', 'key', 'jwt_secret', 'your-256-bit-secret'
28
+ </weak_secrets>
29
+
30
+ <kid_manipulation>
31
+ - SQL Injection: "kid": "key' UNION SELECT 'secret'--"
32
+ - Command injection: "kid": "|sleep 10"
33
+ - Path traversal: "kid": "../../../../../../dev/null"
34
+ </kid_manipulation>
35
+ </common_attacks>
36
+
37
+ <advanced_techniques>
38
+ <jwk_injection>
39
+ Embed public key in token header:
40
+ {"jwk": {"kty": "RSA", "n": "your-public-key-n", "e": "AQAB"}}
41
+ </jwk_injection>
42
+
43
+ <jku_manipulation>
44
+ Set jku/x5u to attacker-controlled URL hosting malicious JWKS
45
+ </jku_manipulation>
46
+
47
+ <timing_attacks>
48
+ Extract signature byte-by-byte using verification timing differences
49
+ </timing_attacks>
50
+ </advanced_techniques>
51
+
52
+ <oauth_vulnerabilities>
53
+ <authorization_code_theft>
54
+ - Exploit redirect_uri with open redirects, subdomain takeover, parameter pollution
55
+ - Missing/predictable state parameter = CSRF
56
+ - PKCE downgrade: remove code_challenge parameter
57
+ </authorization_code_theft>
58
+ </oauth_vulnerabilities>
59
+
60
+ <saml_attacks>
61
+ - Signature exclusion: remove signature element
62
+ - Signature wrapping: inject assertions
63
+ - XXE in SAML responses
64
+ </saml_attacks>
65
+
66
+ <session_attacks>
67
+ - Session fixation: force known session ID
68
+ - Session puzzling: mix different session objects
69
+ - Race conditions in session generation
70
+ </session_attacks>
71
+
72
+ <password_reset_flaws>
73
+ - Predictable tokens: MD5(timestamp), sequential numbers
74
+ - Host header injection for reset link poisoning
75
+ - Race condition resets
76
+ </password_reset_flaws>
77
+
78
+ <mfa_bypass>
79
+ - Response manipulation: change success:false to true
80
+ - Status code manipulation: 403 to 200
81
+ - Brute force with no rate limiting
82
+ - Backup code abuse
83
+ </mfa_bypass>
84
+
85
+ <advanced_bypasses>
86
+ <unicode_normalization>
87
+ Different representations: admin@example.com (fullwidth), аdmin@example.com (Cyrillic)
88
+ </unicode_normalization>
89
+
90
+ <authentication_chaining>
91
+ - JWT + SQLi: kid parameter with SQL injection
92
+ - OAuth + XSS: steal tokens via XSS
93
+ - SAML + XXE + SSRF: chain for internal access
94
+ </authentication_chaining>
95
+ </advanced_bypasses>
96
+
97
+ <tools>
98
+ - jwt_tool: Comprehensive JWT testing
99
+ - Check endpoints: /login, /oauth/authorize, /saml/login, /.well-known/openid-configuration, /jwks.json
100
+ </tools>
101
+
102
+ <validation>
103
+ To confirm authentication flaw:
104
+ 1. Demonstrate account access without credentials
105
+ 2. Show privilege escalation
106
+ 3. Prove token forgery works
107
+ 4. Bypass authentication/2FA requirements
108
+ 5. Maintain persistent access
109
+ </validation>
110
+
111
+ <false_positives>
112
+ NOT a vulnerability if:
113
+ - Requires valid credentials
114
+ - Only affects own session
115
+ - Proper signature validation
116
+ - Token expiration enforced
117
+ - Rate limiting prevents brute force
118
+ </false_positives>
119
+
120
+ <impact>
121
+ - Account takeover: access other users' accounts
122
+ - Privilege escalation: user to admin
123
+ - Token forgery: create valid tokens
124
+ - Bypass mechanisms: skip auth/2FA
125
+ - Persistent access: survives logout
126
+ </impact>
127
+
128
+ <remember>Focus on RS256->HS256, weak secrets, and none algorithm first. Modern apps use multiple auth methods simultaneously - find gaps in integration.</remember>
129
+ </authentication_jwt_guide>