Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion strix/agents/StrixAgent/system_prompt.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ OPERATIONAL PRINCIPLES:
- Chain vulnerabilities for maximum impact
- Consider business logic and context in exploitation
- NEVER skip think tool - it's your most important tool for reasoning and success
- SKILL TOOLS (need-based, applies to all agents): list_skills—MUST call before create_agent(skills=...) or before load_skills when you need valid skill names. Do NOT call at task start. load_skills—MUST call before testing any vulnerability type or technology NOT already in your <specialized_knowledge> section. If expertise is already there, proceed. create_agent—when spawning testing/exploitation agents, always pass skills param; call list_skills first to get valid names; match skills to recon findings.
- WORK RELENTLESSLY - Don't stop until you've found something significant
- Try multiple approaches simultaneously - don't wait for one to fail
- Continuously research payloads, bypasses, and exploitation techniques with the web_search tool; integrate findings into automated sprays and validation
Expand Down Expand Up @@ -167,6 +168,8 @@ You have access to comprehensive guides for each vulnerability type above. Use t
- Tool usage and custom scripts
- Post-exploitation strategies

LOAD SKILLS BEFORE TESTING: Check your <specialized_knowledge> section. If the vulnerability type or technology is already there, proceed. If NOT, call list_skills (to get valid names) then load_skills BEFORE running tests. Never test without the relevant expertise loaded.

BUG BOUNTY MINDSET:
- Think like a bug bounty hunter - only report what would earn rewards
- One critical vulnerability > 100 informational findings
Expand All @@ -187,6 +190,7 @@ AGENT ISOLATION & SANDBOXING:
MANDATORY INITIAL PHASES:

BLACK-BOX TESTING - PHASE 1 (RECON & MAPPING):
When spawning testing agents: call list_skills first to get valid skill names, then create_agent with skills param matching recon findings.
- COMPLETE full reconnaissance: subdomain enumeration, port scanning, service detection
- MAP entire attack surface: all endpoints, parameters, APIs, forms, inputs
- CRAWL thoroughly: spider all pages (authenticated and unauthenticated), discover hidden paths, analyze JS files
Expand All @@ -201,14 +205,23 @@ WHITE-BOX TESTING - PHASE 1 (CODE UNDERSTANDING):
- REVIEW dependencies and third-party libraries
- ONLY AFTER full code comprehension → proceed to vulnerability testing

SKILL MATCHING (recon findings → create_agent skills param):
- Forms, inputs, search, filters → sql_injection, xss
- Login, auth, JWT, sessions → authentication_jwt, business_logic
- GraphQL API → graphql
- FastAPI, Next.js, etc. → fastapi, nextjs
- Firebase, Supabase → firebase_firestore, supabase
- File uploads, path params → path_traversal_lfi_rfi, insecure_file_uploads
- SSRF, XXE, RCE, IDOR, CSRF, etc. → use skill name matching the vulnerability type

PHASE 2 - SYSTEMATIC VULNERABILITY TESTING:
- CREATE SPECIALIZED SUBAGENT for EACH vulnerability type × EACH component
- Each agent focuses on ONE vulnerability type in ONE specific location
- EVERY detected vulnerability MUST spawn its own validation subagent

SIMPLE WORKFLOW RULES:

1. **ALWAYS CREATE AGENTS IN TREES** - Never work alone, always spawn subagents
1. **ALWAYS CREATE AGENTS IN TREES** - Never work alone, always spawn subagents. Before create_agent(skills=...), call list_skills to get valid names.
2. **BLACK-BOX**: Discovery → Validation → Reporting (3 agents per vulnerability)
3. **WHITE-BOX**: Discovery → Validation → Reporting → Fixing (4 agents per vulnerability)
4. **MULTIPLE VULNS = MULTIPLE CHAINS** - Each vulnerability finding gets its own validation chain
Expand Down Expand Up @@ -265,6 +278,7 @@ CRITICAL RULES:
- **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 1–3 skills, up to 5 for complex contexts
- **SKILL ADAPTATION**: Check <specialized_knowledge> before testing. If expertise is missing, call list_skills then load_skills. If you orchestrate only (spawn agents, don't test), you never need load_skills.
- **NO GENERIC AGENTS** - Avoid creating broad, multi-purpose agents that dilute focus

AGENT SPECIALIZATION EXAMPLES:
Expand Down Expand Up @@ -352,6 +366,15 @@ Example (agent creation tool):
<parameter=skills>xss</parameter>
</function>

Example (call list_skills before create_agent(skills=...) or load_skills):
<function=list_skills>
</function>

Example (call load_skills before testing a type not in <specialized_knowledge>):
<function=load_skills>
<parameter=skills>sql_injection</parameter>
</function>

SPRAYING EXECUTION NOTE:
- When performing large payload sprays or fuzzing, encapsulate the entire spraying loop inside a single python or terminal tool call (e.g., a Python script using asyncio/aiohttp). Do not issue one tool call per payload.
- Favor batch-mode CLI tools (sqlmap, ffuf, nuclei, zaproxy, arjun) where appropriate and check traffic via the proxy when beneficial
Expand Down
1 change: 1 addition & 0 deletions strix/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from .proxy import * # noqa: F403
from .python import * # noqa: F403
from .reporting import * # noqa: F403
from .skills import * # noqa: F403
from .terminal import * # noqa: F403
from .thinking import * # noqa: F403
from .todo import * # noqa: F403
Expand Down
7 changes: 7 additions & 0 deletions strix/tools/skills/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .skills_actions import list_skills, load_skills


__all__ = [
"list_skills",
"load_skills",
]
195 changes: 195 additions & 0 deletions strix/tools/skills/skills_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import re
from typing import Any

from strix.skills import (
get_all_skill_names,
get_available_skills,
validate_skill_names,
)
from strix.skills import (
load_skills as load_skills_content,
)
from strix.tools.registry import register_tool
from strix.utils.resource_paths import get_strix_resource_path


_FRONTMATTER_PATTERN = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
_NAME_PATTERN = re.compile(r"^name:\s*(.+)$", re.MULTILINE)
_DESCRIPTION_PATTERN = re.compile(r"^description:\s*(.+)$", re.MULTILINE)


def _extract_frontmatter(content: str) -> dict[str, str] | None:
"""Extract name and description from YAML frontmatter using simple regex parsing."""
match = _FRONTMATTER_PATTERN.match(content)
if not match:
return None

frontmatter_text = match.group(1)
metadata: dict[str, str] = {}

name_match = _NAME_PATTERN.search(frontmatter_text)
if name_match:
metadata["name"] = name_match.group(1).strip()

desc_match = _DESCRIPTION_PATTERN.search(frontmatter_text)
if desc_match:
metadata["description"] = desc_match.group(1).strip()

return metadata if metadata else None


def _get_skill_metadata(skill_name: str, category: str | None = None) -> dict[str, str] | None:
"""Get metadata (name, description) for a specific skill by reading its file."""
skills_dir = get_strix_resource_path("skills")

if category:
skill_path = skills_dir / category / f"{skill_name}.md"
else:
all_categories = get_available_skills()
skill_path = None
for cat, skills in all_categories.items():
if skill_name in skills:
skill_path = skills_dir / cat / f"{skill_name}.md"
break

if not skill_path:
root_candidate = skills_dir / f"{skill_name}.md"
if root_candidate.exists():
skill_path = root_candidate

if skill_path and skill_path.exists():
try:
content = skill_path.read_text()
return _extract_frontmatter(content)
except (FileNotFoundError, OSError):
return None

return None


@register_tool(sandbox_execution=False)
def list_skills(
category: str | None = None,
) -> dict[str, Any]:
"""List available skills, optionally filtered by category.

Always includes name and description metadata for each skill.
"""
try:
available_skills = get_available_skills()

if category:
if category in available_skills:
filtered_skills = {category: available_skills[category]}
else:
categories_str = ", ".join(sorted(available_skills.keys()))
return {
"success": False,
"error": (
f"Category '{category}' not found. Available categories: {categories_str}"
),
"skills_by_category": {},
"all_skills": [],
"categories": list(available_skills.keys()),
"metadata": {},
}
else:
filtered_skills = available_skills

metadata: dict[str, dict[str, str]] = {}
for cat, skills in filtered_skills.items():
for skill_name in skills:
skill_meta = _get_skill_metadata(skill_name, cat)
if skill_meta:
metadata[skill_name] = skill_meta

all_filtered_skills: list[str] = []
for skills in filtered_skills.values():
all_filtered_skills.extend(skills)
all_filtered_skills = sorted(set(all_filtered_skills))

return {
"success": True,
"skills_by_category": filtered_skills,
"all_skills": all_filtered_skills
if not category
else filtered_skills.get(category, []),
"categories": list(available_skills.keys()),
"metadata": metadata,
}
except Exception as e: # noqa: BLE001
return {
"success": False,
"error": f"Failed to list skills: {e}",
"skills_by_category": {},
"all_skills": [],
"categories": [],
"metadata": {},
}


@register_tool(sandbox_execution=False)
def load_skills(
agent_state: Any,
skills: str,
) -> dict[str, Any]:
"""Load skill content dynamically at runtime for immediate use."""
try:
skill_list = [s.strip() for s in skills.split(",") if s.strip()]

if not skill_list:
return {
"success": False,
"error": "No skills specified. Provide comma-separated skill names.",
"loaded_skills": {},
"loaded_count": 0,
"invalid_skills": [],
"warnings": [],
}

def _bare_name(s: str) -> str:
return s.split("/")[-1]

validation = validate_skill_names([_bare_name(s) for s in skill_list])
invalid_bare = set(validation.get("invalid", []))
valid_skills = [s for s in skill_list if _bare_name(s) not in invalid_bare]
invalid_skills = [s for s in skill_list if _bare_name(s) in invalid_bare]

warnings: list[str] = []
if invalid_skills:
available_skills = list(get_all_skill_names())
warnings.append(
f"Invalid skills: {', '.join(invalid_skills)}. "
f"Available skills: {', '.join(sorted(available_skills))}"
)

loaded_content = load_skills_content(valid_skills)

loaded_skill_names = set(loaded_content.keys())
requested_skill_names = {s.split("/")[-1] for s in valid_skills}
missing_skills = requested_skill_names - loaded_skill_names
if missing_skills:
warnings.append(f"Some skills could not be loaded: {', '.join(missing_skills)}")

result: dict[str, Any] = {
"success": len(loaded_content) > 0,
"loaded_skills": loaded_content,
"loaded_count": len(loaded_content),
"invalid_skills": invalid_skills,
"warnings": warnings,
}
if not result["success"] and not loaded_content:
result["error"] = (
"No skills could be loaded. "
+ (warnings[0] if warnings else "Check skill names with list_skills.")
)
return result
except Exception as e: # noqa: BLE001
return {
"success": False,
"error": f"Failed to load skills: {e}",
"loaded_skills": {},
"loaded_count": 0,
"invalid_skills": [],
"warnings": [],
}
Loading