Refactor(skills): rename prompt modules to skills and update documentation

This commit is contained in:
0xallam
2026-01-06 17:50:15 -08:00
parent f48def1f9e
commit 7af1180a30
43 changed files with 235 additions and 238 deletions

View File

@@ -39,14 +39,14 @@ Thank you for your interest in contributing to Strix! This guide will help you g
poetry run strix --target https://example.com
```
## 📚 Contributing Prompt Modules
## 📚 Contributing Skills
Prompt modules are specialized knowledge packages that enhance agent capabilities. See [strix/prompts/README.md](strix/prompts/README.md) for detailed guidelines.
Skills are specialized knowledge packages that enhance agent capabilities. See [strix/skills/README.md](strix/skills/README.md) for detailed guidelines.
### Quick Guide
1. **Choose the right category** (`/vulnerabilities`, `/frameworks`, `/technologies`, etc.)
2. **Create a** `.jinja` file with your prompts
2. **Create a** `.jinja` file with your skill content
3. **Include practical examples** - Working payloads, commands, or test cases
4. **Provide validation methods** - How to confirm findings and avoid false positives
5. **Submit via PR** with clear description

View File

@@ -219,11 +219,11 @@ export PERPLEXITY_API_KEY="your-api-key" # for search capabilities
## 📚 Documentation
Full documentation is available at **[docs.strix.ai](https://docs.strix.ai)** — including detailed guides for usage, CI/CD integrations, prompt modules, and advanced configuration.
Full documentation is available at **[docs.strix.ai](https://docs.strix.ai)** — including detailed guides for usage, CI/CD integrations, skills, and advanced configuration.
## 🤝 Contributing
We welcome contributions of code, docs, and new prompt modules - check out our [Contributing Guide](https://docs.strix.ai/contributing) to get started or open a [pull request](https://github.com/usestrix/strix/pulls)/[issue](https://github.com/usestrix/strix/issues).
We welcome contributions of code, docs, and new skills - check out our [Contributing Guide](https://docs.strix.ai/contributing) to get started or open a [pull request](https://github.com/usestrix/strix/pulls)/[issue](https://github.com/usestrix/strix/issues).
## 👥 Join Our Community

View File

@@ -122,7 +122,7 @@ hiddenimports = [
'strix.tools.registry',
'strix.tools.executor',
'strix.tools.argument_parser',
'strix.prompts',
'strix.skills',
]
hiddenimports += collect_submodules('litellm')

View File

@@ -8,13 +8,13 @@ class StrixAgent(BaseAgent):
max_iterations = 300
def __init__(self, config: dict[str, Any]):
default_modules = []
default_skills = []
state = config.get("state")
if state is None or (hasattr(state, "parent_id") and state.parent_id is None):
default_modules = ["root_agent"]
default_skills = ["root_agent"]
self.default_llm_config = LLMConfig(prompt_modules=default_modules)
self.default_llm_config = LLMConfig(skills=default_skills)
super().__init__(config)

View File

@@ -263,25 +263,25 @@ CRITICAL RULES:
- **ONE AGENT = ONE TASK** - Don't let agents do multiple unrelated jobs
- **SPAWN REACTIVELY** - Create new agents based on what you discover
- **ONLY REPORTING AGENTS** can use create_vulnerability_report tool
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized; prefer 13 prompt modules, up to 5 for complex contexts
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized; prefer 13 skills, up to 5 for complex contexts
- **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)
- "SQLi Validation Agent" with skills: sql_injection
- "XSS Discovery Agent" with skills: xss
- "Auth Testing Agent" with skills: authentication_jwt, business_logic
- "SSRF + XXE Agent" with skills: 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 5 prompt modules (violates constraints)
- "General Web Testing Agent" with skills: sql_injection, xss, csrf, ssrf, authentication_jwt (too broad)
- "Everything Agent" with skills: all available skills (completely unfocused)
- Any agent with more than 5 skills (violates constraints)
FOCUS PRINCIPLES:
- Each agent should have deep expertise in 1-3 related vulnerability types
- Agents with single modules have the deepest specialization
- Agents with single skills 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
@@ -323,7 +323,7 @@ Example (agent creation tool):
<function=create_agent>
<parameter=task>Perform targeted XSS testing on the search endpoint</parameter>
<parameter=name>XSS Discovery Agent</parameter>
<parameter=prompt_modules>xss</parameter>
<parameter=skills>xss</parameter>
</function>
SPRAYING EXECUTION NOTE:
@@ -392,12 +392,12 @@ Directories:
Default user: pentester (sudo available)
</environment>
{% if loaded_module_names %}
{% if loaded_skill_names %}
<specialized_knowledge>
{# Dynamic prompt modules loaded based on agent specialization #}
{# Dynamic skills loaded based on agent specialization #}
{% for module_name in loaded_module_names %}
{{ get_module(module_name) }}
{% for skill_name in loaded_skill_names %}
{{ get_skill(skill_name) }}
{% endfor %}
</specialized_knowledge>

View File

@@ -6,7 +6,7 @@ class LLMConfig:
self,
model_name: str | None = None,
enable_prompt_caching: bool = True,
prompt_modules: list[str] | None = None,
skills: list[str] | None = None,
timeout: int | None = None,
scan_mode: str = "deep",
):
@@ -16,7 +16,7 @@ class LLMConfig:
raise ValueError("STRIX_LLM environment variable must be set and not empty")
self.enable_prompt_caching = enable_prompt_caching
self.prompt_modules = prompt_modules or []
self.skills = skills or []
self.timeout = timeout or int(os.getenv("LLM_TIMEOUT", "300"))

View File

@@ -20,7 +20,7 @@ from strix.llm.config import LLMConfig
from strix.llm.memory_compressor import MemoryCompressor
from strix.llm.request_queue import get_global_queue
from strix.llm.utils import _truncate_to_first_function, parse_tool_invocations
from strix.prompts import load_prompt_modules
from strix.skills import load_skills
from strix.tools import get_tools_prompt
@@ -116,29 +116,29 @@ class LLM:
if agent_name:
prompt_dir = Path(__file__).parent.parent / "agents" / agent_name
prompts_dir = Path(__file__).parent.parent / "prompts"
skills_dir = Path(__file__).parent.parent / "skills"
loader = FileSystemLoader([prompt_dir, prompts_dir])
loader = FileSystemLoader([prompt_dir, skills_dir])
self.jinja_env = Environment(
loader=loader,
autoescape=select_autoescape(enabled_extensions=(), default_for_string=False),
)
try:
modules_to_load = list(self.config.prompt_modules or [])
modules_to_load.append(f"scan_modes/{self.config.scan_mode}")
skills_to_load = list(self.config.skills or [])
skills_to_load.append(f"scan_modes/{self.config.scan_mode}")
prompt_module_content = load_prompt_modules(modules_to_load, self.jinja_env)
skill_content = load_skills(skills_to_load, self.jinja_env)
def get_module(name: str) -> str:
return prompt_module_content.get(name, "")
def get_skill(name: str) -> str:
return skill_content.get(name, "")
self.jinja_env.globals["get_module"] = get_module
self.jinja_env.globals["get_skill"] = get_skill
self.system_prompt = self.jinja_env.get_template("system_prompt.jinja").render(
get_tools_prompt=get_tools_prompt,
loaded_module_names=list(prompt_module_content.keys()),
**prompt_module_content,
loaded_skill_names=list(skill_content.keys()),
**skill_content,
)
except (FileNotFoundError, OSError, ValueError) as e:
logger.warning(f"Failed to load system prompt for {agent_name}: {e}")

View File

@@ -1,64 +0,0 @@
# 📚 Strix Prompt Modules
## 🎯 Overview
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.
---
## 🏗️ Architecture
### How Prompts Work
When an agent is created, it can load up to 5 specialized prompt modules relevant to the specific subtask and context at hand:
```python
# Agent creation with specialized modules
create_agent(
task="Test authentication mechanisms in API",
name="Auth Specialist",
prompt_modules="authentication_jwt,business_logic"
)
```
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.
---
## 📁 Module Categories
| Category | Purpose |
|----------|---------|
| **`/vulnerabilities`** | Advanced testing techniques for core vulnerability classes like authentication bypasses, business logic flaws, and race conditions |
| **`/frameworks`** | Specific testing methods for popular frameworks e.g. Django, Express, FastAPI, and Next.js |
| **`/technologies`** | Specialized techniques for third-party services such as Supabase, Firebase, Auth0, and payment gateways |
| **`/protocols`** | Protocol-specific testing patterns for GraphQL, WebSocket, OAuth, and other communication standards |
| **`/cloud`** | Cloud provider security testing for AWS, Azure, GCP, and Kubernetes environments |
| **`/reconnaissance`** | Advanced information gathering and enumeration techniques for comprehensive attack surface mapping |
| **`/custom`** | Community-contributed modules for specialized or industry-specific testing scenarios |
---
## 🎨 Creating New Modules
### What Should a Module Contain?
A good prompt module is a structured knowledge package that typically includes:
- **Advanced techniques** - Non-obvious methods specific to the task and domain
- **Practical examples** - Working payloads, commands, or test cases with variations
- **Validation methods** - How to confirm findings and avoid false positives
- **Context-specific insights** - Environment and version nuances, configuration-dependent behavior, and edge cases
Modules use XML-style tags for structure and focus on deep, specialized knowledge that significantly enhances agent capabilities for that specific context.
---
## 🤝 Contributing
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.
---
> [!NOTE]
> **Work in Progress** - We're actively expanding the prompt module collection with specialized techniques and new categories.

View File

@@ -1,109 +0,0 @@
from pathlib import Path
from jinja2 import Environment
def get_available_prompt_modules() -> dict[str, list[str]]:
modules_dir = Path(__file__).parent
available_modules = {}
for category_dir in modules_dir.iterdir():
if category_dir.is_dir() and not category_dir.name.startswith("__"):
category_name = category_dir.name
modules = []
for file_path in category_dir.glob("*.jinja"):
module_name = file_path.stem
modules.append(module_name)
if modules:
available_modules[category_name] = sorted(modules)
return available_modules
def get_all_module_names() -> set[str]:
all_modules = set()
for category_modules in get_available_prompt_modules().values():
all_modules.update(category_modules)
return all_modules
def validate_module_names(module_names: list[str]) -> dict[str, list[str]]:
available_modules = get_all_module_names()
valid_modules = []
invalid_modules = []
for module_name in module_names:
if module_name in available_modules:
valid_modules.append(module_name)
else:
invalid_modules.append(module_name)
return {"valid": valid_modules, "invalid": invalid_modules}
def generate_modules_description() -> str:
available_modules = get_available_prompt_modules()
if not available_modules:
return "No prompt modules available"
all_module_names = get_all_module_names()
if not all_module_names:
return "No prompt modules available"
sorted_modules = sorted(all_module_names)
modules_str = ", ".join(sorted_modules)
description = (
f"List of prompt modules to load for this agent (max 5). Available modules: {modules_str}. "
)
example_modules = sorted_modules[:2]
if example_modules:
example = f"Example: {', '.join(example_modules)} for specialized agent"
description += example
return description
def load_prompt_modules(module_names: list[str], jinja_env: Environment) -> dict[str, str]:
import logging
logger = logging.getLogger(__name__)
module_content = {}
prompts_dir = Path(__file__).parent
available_modules = get_available_prompt_modules()
for module_name in module_names:
try:
module_path = None
if "/" in module_name:
module_path = f"{module_name}.jinja"
else:
for category, modules in available_modules.items():
if module_name in modules:
module_path = f"{category}/{module_name}.jinja"
break
if not module_path:
root_candidate = f"{module_name}.jinja"
if (prompts_dir / root_candidate).exists():
module_path = root_candidate
if module_path and (prompts_dir / module_path).exists():
template = jinja_env.get_template(module_path)
var_name = module_name.split("/")[-1]
module_content[var_name] = template.render()
logger.info(f"Loaded prompt module: {module_name} -> {var_name}")
else:
logger.warning(f"Prompt module not found: {module_name}")
except (FileNotFoundError, OSError, ValueError) as e:
logger.warning(f"Failed to load prompt module {module_name}: {e}")
return module_content

64
strix/skills/README.md Normal file
View File

@@ -0,0 +1,64 @@
# 📚 Strix Skills
## 🎯 Overview
Skills are specialized knowledge packages that enhance Strix agents with deep expertise in specific vulnerability types, technologies, and testing methodologies. Each skill provides advanced techniques, practical examples, and validation methods that go beyond baseline security knowledge.
---
## 🏗️ Architecture
### How Skills Work
When an agent is created, it can load up to 5 specialized skills relevant to the specific subtask and context at hand:
```python
# Agent creation with specialized skills
create_agent(
task="Test authentication mechanisms in API",
name="Auth Specialist",
skills="authentication_jwt,business_logic"
)
```
The skills 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.
---
## 📁 Skill Categories
| Category | Purpose |
|----------|---------|
| **`/vulnerabilities`** | Advanced testing techniques for core vulnerability classes like authentication bypasses, business logic flaws, and race conditions |
| **`/frameworks`** | Specific testing methods for popular frameworks e.g. Django, Express, FastAPI, and Next.js |
| **`/technologies`** | Specialized techniques for third-party services such as Supabase, Firebase, Auth0, and payment gateways |
| **`/protocols`** | Protocol-specific testing patterns for GraphQL, WebSocket, OAuth, and other communication standards |
| **`/cloud`** | Cloud provider security testing for AWS, Azure, GCP, and Kubernetes environments |
| **`/reconnaissance`** | Advanced information gathering and enumeration techniques for comprehensive attack surface mapping |
| **`/custom`** | Community-contributed skills for specialized or industry-specific testing scenarios |
---
## 🎨 Creating New Skills
### What Should a Skill Contain?
A good skill is a structured knowledge package that typically includes:
- **Advanced techniques** - Non-obvious methods specific to the task and domain
- **Practical examples** - Working payloads, commands, or test cases with variations
- **Validation methods** - How to confirm findings and avoid false positives
- **Context-specific insights** - Environment and version nuances, configuration-dependent behavior, and edge cases
Skills use XML-style tags for structure and focus on deep, specialized knowledge that significantly enhances agent capabilities for that specific context.
---
## 🤝 Contributing
Community contributions are more than welcome — contribute new skills 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.
---
> [!NOTE]
> **Work in Progress** - We're actively expanding the skills collection with specialized techniques and new categories.

107
strix/skills/__init__.py Normal file
View File

@@ -0,0 +1,107 @@
from pathlib import Path
from jinja2 import Environment
def get_available_skills() -> dict[str, list[str]]:
skills_dir = Path(__file__).parent
available_skills = {}
for category_dir in skills_dir.iterdir():
if category_dir.is_dir() and not category_dir.name.startswith("__"):
category_name = category_dir.name
skills = []
for file_path in category_dir.glob("*.jinja"):
skill_name = file_path.stem
skills.append(skill_name)
if skills:
available_skills[category_name] = sorted(skills)
return available_skills
def get_all_skill_names() -> set[str]:
all_skills = set()
for category_skills in get_available_skills().values():
all_skills.update(category_skills)
return all_skills
def validate_skill_names(skill_names: list[str]) -> dict[str, list[str]]:
available_skills = get_all_skill_names()
valid_skills = []
invalid_skills = []
for skill_name in skill_names:
if skill_name in available_skills:
valid_skills.append(skill_name)
else:
invalid_skills.append(skill_name)
return {"valid": valid_skills, "invalid": invalid_skills}
def generate_skills_description() -> str:
available_skills = get_available_skills()
if not available_skills:
return "No skills available"
all_skill_names = get_all_skill_names()
if not all_skill_names:
return "No skills available"
sorted_skills = sorted(all_skill_names)
skills_str = ", ".join(sorted_skills)
description = f"List of skills to load for this agent (max 5). Available skills: {skills_str}. "
example_skills = sorted_skills[:2]
if example_skills:
example = f"Example: {', '.join(example_skills)} for specialized agent"
description += example
return description
def load_skills(skill_names: list[str], jinja_env: Environment) -> dict[str, str]:
import logging
logger = logging.getLogger(__name__)
skill_content = {}
skills_dir = Path(__file__).parent
available_skills = get_available_skills()
for skill_name in skill_names:
try:
skill_path = None
if "/" in skill_name:
skill_path = f"{skill_name}.jinja"
else:
for category, skills in available_skills.items():
if skill_name in skills:
skill_path = f"{category}/{skill_name}.jinja"
break
if not skill_path:
root_candidate = f"{skill_name}.jinja"
if (skills_dir / root_candidate).exists():
skill_path = root_candidate
if skill_path and (skills_dir / skill_path).exists():
template = jinja_env.get_template(skill_path)
var_name = skill_name.split("/")[-1]
skill_content[var_name] = template.render()
logger.info(f"Loaded skill: {skill_name} -> {var_name}")
else:
logger.warning(f"Skill not found: {skill_name}")
except (FileNotFoundError, OSError, ValueError) as e:
logger.warning(f"Failed to load skill {skill_name}: {e}")
return skill_content

View File

@@ -190,36 +190,35 @@ def create_agent(
task: str,
name: str,
inherit_context: bool = True,
prompt_modules: str | None = None,
skills: str | None = None,
) -> dict[str, Any]:
try:
parent_id = agent_state.agent_id
module_list = []
if prompt_modules:
module_list = [m.strip() for m in prompt_modules.split(",") if m.strip()]
skill_list = []
if skills:
skill_list = [s.strip() for s in skills.split(",") if s.strip()]
if len(module_list) > 5:
if len(skill_list) > 5:
return {
"success": False,
"error": (
"Cannot specify more than 5 prompt modules for an agent "
"(use comma-separated format)"
"Cannot specify more than 5 skills for an agent (use comma-separated format)"
),
"agent_id": None,
}
if module_list:
from strix.prompts import get_all_module_names, validate_module_names
if skill_list:
from strix.skills import get_all_skill_names, validate_skill_names
validation = validate_module_names(module_list)
validation = validate_skill_names(skill_list)
if validation["invalid"]:
available_modules = list(get_all_module_names())
available_skills = list(get_all_skill_names())
return {
"success": False,
"error": (
f"Invalid prompt modules: {validation['invalid']}. "
f"Available modules: {', '.join(available_modules)}"
f"Invalid skills: {validation['invalid']}. "
f"Available skills: {', '.join(available_skills)}"
),
"agent_id": None,
}
@@ -240,7 +239,7 @@ def create_agent(
if hasattr(parent_agent.llm_config, "scan_mode"):
scan_mode = parent_agent.llm_config.scan_mode
llm_config = LLMConfig(prompt_modules=module_list, timeout=timeout, scan_mode=scan_mode)
llm_config = LLMConfig(skills=skill_list, timeout=timeout, scan_mode=scan_mode)
agent_config = {
"llm_config": llm_config,

View File

@@ -79,8 +79,8 @@ Only create a new agent if no existing agent is handling the specific task.</des
<parameter name="inherit_context" type="boolean" required="false">
<description>Whether the new agent should inherit parent's conversation history and context</description>
</parameter>
<parameter name="prompt_modules" type="string" required="false">
<description>Comma-separated list of prompt modules to use for the agent (MAXIMUM 5 modules allowed). Most agents should have at least one module in order to be useful. Agents should be highly specialized - use 1-3 related modules; up to 5 for complex contexts. {{DYNAMIC_MODULES_DESCRIPTION}}</description>
<parameter name="skills" type="string" required="false">
<description>Comma-separated list of skills to use for the agent (MAXIMUM 5 skills allowed). Most agents should have at least one skill in order to be useful. Agents should be highly specialized - use 1-3 related skills; up to 5 for complex contexts. {{DYNAMIC_SKILLS_DESCRIPTION}}</description>
</parameter>
</parameters>
<returns type="Dict[str, Any]">
@@ -92,30 +92,30 @@ Only create a new agent if no existing agent is handling the specific task.</des
<parameter=task>Validate and exploit the suspected SQL injection vulnerability found in
the login form. Confirm exploitability and document proof of concept.</parameter>
<parameter=name>SQLi Validator</parameter>
<parameter=prompt_modules>sql_injection</parameter>
<parameter=skills>sql_injection</parameter>
</function>
<function=create_agent>
<parameter=task>Test authentication mechanisms, JWT implementation, and session management
for security vulnerabilities and bypass techniques.</parameter>
<parameter=name>Auth Specialist</parameter>
<parameter=prompt_modules>authentication_jwt, business_logic</parameter>
<parameter=skills>authentication_jwt, business_logic</parameter>
</function>
# Example of single-module specialization (most focused)
# Example of single-skill 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>
<parameter=skills>xss</parameter>
</function>
# Example of up to 5 related modules (borderline acceptable)
# Example of up to 5 related skills (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>
<parameter=skills>ssrf, xxe, rce</parameter>
</function>
</examples>
</tool>

View File

@@ -23,17 +23,17 @@ class ImplementedInClientSideOnlyError(Exception):
def _process_dynamic_content(content: str) -> str:
if "{{DYNAMIC_MODULES_DESCRIPTION}}" in content:
if "{{DYNAMIC_SKILLS_DESCRIPTION}}" in content:
try:
from strix.prompts import generate_modules_description
from strix.skills import generate_skills_description
modules_description = generate_modules_description()
content = content.replace("{{DYNAMIC_MODULES_DESCRIPTION}}", modules_description)
skills_description = generate_skills_description()
content = content.replace("{{DYNAMIC_SKILLS_DESCRIPTION}}", skills_description)
except ImportError:
logger.warning("Could not import prompts utilities for dynamic schema generation")
logger.warning("Could not import skills utilities for dynamic schema generation")
content = content.replace(
"{{DYNAMIC_MODULES_DESCRIPTION}}",
"List of prompt modules to load for this agent (max 5). Module discovery failed.",
"{{DYNAMIC_SKILLS_DESCRIPTION}}",
"List of skills to load for this agent (max 5). Skill discovery failed.",
)
return content