docs: restore tool guidance into docstrings, drop prompt tool-format boilerplate
Port the prose guidance that previously lived in the deleted *_actions_schema.xml files into per-tool docstrings, so the SDK's auto-generated function schema carries the same domain knowledge (HTTPQL syntax, Caido sitemap kinds, browser persistence/JS rules, agent specialization caps, customer-facing report rules, CVSS/CWE guidance, etc.) without any custom prompt scaffolding. Strip the <tool_usage> block from system_prompt.jinja — XML format guidance, the "CRITICAL RULES" 0-8 list, and the </function> closing-tag reminder all contradicted the SDK's native JSON function-calling protocol. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -359,77 +359,6 @@ PERSISTENCE IS MANDATORY:
|
|||||||
- There are ALWAYS more attack vectors to explore
|
- There are ALWAYS more attack vectors to explore
|
||||||
</multi_agent_system>
|
</multi_agent_system>
|
||||||
|
|
||||||
<tool_usage>
|
|
||||||
Tool call format:
|
|
||||||
<function=tool_name>
|
|
||||||
<parameter=param_name>value</parameter>
|
|
||||||
</function>
|
|
||||||
|
|
||||||
CRITICAL RULES:
|
|
||||||
{% if interactive %}
|
|
||||||
0. When using tools, include exactly one tool call per message. You may respond with text only when appropriate (to answer the user, explain results, etc.).
|
|
||||||
{% else %}
|
|
||||||
0. While active in the agent loop, EVERY message you output MUST be a single tool call. Do not send plain text-only responses.
|
|
||||||
{% endif %}
|
|
||||||
1. Exactly one tool call per message — never include more than one <function>...</function> block in a single LLM message.
|
|
||||||
2. Tool call must be last in message
|
|
||||||
3. EVERY tool call MUST end with </function>. This is MANDATORY. Never omit the closing tag. End your response immediately after </function>.
|
|
||||||
4. Use ONLY the exact format shown above. NEVER use JSON/YAML/INI or any other syntax for tools or parameters.
|
|
||||||
5. When sending ANY multi-line content in tool parameters, use real newlines (actual line breaks). Do NOT emit literal "\n" sequences. Literal "\n" instead of real line breaks will cause tools to fail.
|
|
||||||
6. Tool names must match exactly the tool "name" defined (no module prefixes, dots, or variants).
|
|
||||||
7. Parameters must use <parameter=param_name>value</parameter> exactly. Do NOT pass parameters as JSON or key:value lines. Do NOT add quotes/braces around values.
|
|
||||||
{% if interactive %}
|
|
||||||
8. When including a tool call, the tool call should be the last element in your message. You may include brief explanatory text before it.
|
|
||||||
{% else %}
|
|
||||||
8. Do NOT wrap tool calls in markdown/code fences or add any text before or after the tool block.
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
CORRECT format — use this EXACTLY:
|
|
||||||
<function=tool_name>
|
|
||||||
<parameter=param_name>value</parameter>
|
|
||||||
</function>
|
|
||||||
|
|
||||||
WRONG formats — NEVER use these:
|
|
||||||
- <invoke name="tool_name"><parameter name="param_name">value</parameter></invoke>
|
|
||||||
- <function_calls><invoke name="tool_name">...</invoke></function_calls>
|
|
||||||
- <tool_call><tool_name>...</tool_name></tool_call>
|
|
||||||
- {"tool_name": {"param_name": "value"}}
|
|
||||||
- ```<function=tool_name>...</function>```
|
|
||||||
- <function=tool_name>value_without_parameter_tags</function>
|
|
||||||
|
|
||||||
EVERY argument MUST be wrapped in <parameter=name>...</parameter> tags. NEVER put values directly in the function body without parameter tags. This WILL cause the tool call to fail.
|
|
||||||
|
|
||||||
Do NOT emit any extra XML tags in your output. In particular:
|
|
||||||
- NO <thinking>...</thinking> or <thought>...</thought> blocks
|
|
||||||
- NO <scratchpad>...</scratchpad> or <reasoning>...</reasoning> blocks
|
|
||||||
- NO <answer>...</answer> or <response>...</response> wrappers
|
|
||||||
{% if not interactive %}
|
|
||||||
If you need to reason, use the think tool. Your raw output must contain ONLY the tool call — no surrounding XML tags.
|
|
||||||
{% else %}
|
|
||||||
If you need to reason, use the think tool. When using tools, do not add surrounding XML tags.
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
Notice: use <function=X> NOT <invoke name="X">, use <parameter=X> NOT <parameter name="X">, use </function> NOT </invoke>.
|
|
||||||
|
|
||||||
Example (terminal tool):
|
|
||||||
<function=terminal_execute>
|
|
||||||
<parameter=command>nmap -sV -p 1-1000 target.com</parameter>
|
|
||||||
</function>
|
|
||||||
|
|
||||||
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=skills>xss</parameter>
|
|
||||||
</function>
|
|
||||||
|
|
||||||
SPRAYING EXECUTION NOTE:
|
|
||||||
- When performing large payload sprays or fuzzing, encapsulate the entire spraying loop inside a single python tool call when you are writing Python logic (for example asyncio/aiohttp). Use terminal tool only when invoking an external CLI/fuzzer. 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
|
|
||||||
|
|
||||||
REMINDER: Always close each tool call with </function> before going into the next. Incomplete tool calls will fail.
|
|
||||||
</tool_usage>
|
|
||||||
|
|
||||||
<environment>
|
<environment>
|
||||||
Docker container with Kali Linux and comprehensive security tools:
|
Docker container with Kali Linux and comprehensive security tools:
|
||||||
|
|
||||||
|
|||||||
@@ -57,11 +57,14 @@ def _dump(result: dict[str, Any]) -> str:
|
|||||||
|
|
||||||
@strix_tool(timeout=30)
|
@strix_tool(timeout=30)
|
||||||
async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
||||||
"""Render the multi-agent tree starting from each root.
|
"""Print the multi-agent tree — every agent, its parent, its status.
|
||||||
|
|
||||||
Output is a single string the model can parse: indented bullet list,
|
Use before spawning a new agent (don't duplicate work — check whether
|
||||||
one line per agent, status in brackets. Roots are agents whose
|
something specialized for that task already exists) and any time you
|
||||||
``parent_of[id]`` is ``None``.
|
want a snapshot of who's still ``running`` / ``waiting`` /
|
||||||
|
``completed`` / ``crashed`` / ``stopped``. Output is an indented
|
||||||
|
bullet list with status in brackets; the agent that called this tool
|
||||||
|
is marked ``← you``.
|
||||||
"""
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
@@ -107,7 +110,17 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
|||||||
|
|
||||||
@strix_tool(timeout=30)
|
@strix_tool(timeout=30)
|
||||||
async def agent_status(ctx: RunContextWrapper, agent_id: str) -> str:
|
async def agent_status(ctx: RunContextWrapper, agent_id: str) -> str:
|
||||||
"""Inspect one agent's lifecycle state and pending message count."""
|
"""Look up one agent's lifecycle state + pending message count.
|
||||||
|
|
||||||
|
Use when you need precise state on a specific agent (e.g., "is the
|
||||||
|
XSS specialist still going?") rather than the full tree view.
|
||||||
|
Returns ``status`` (``running`` / ``waiting`` / ``completed`` /
|
||||||
|
``crashed`` / ``stopped``), ``parent_id``, and ``pending_messages``.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent_id: The 8-char id from ``view_agent_graph`` /
|
||||||
|
``create_agent``.
|
||||||
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
if bus is None:
|
if bus is None:
|
||||||
@@ -141,12 +154,29 @@ async def send_message_to_agent(
|
|||||||
message_type: Literal["query", "instruction", "information"] = "information",
|
message_type: Literal["query", "instruction", "information"] = "information",
|
||||||
priority: Literal["low", "normal", "high", "urgent"] = "normal",
|
priority: Literal["low", "normal", "high", "urgent"] = "normal",
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Queue a message for another agent's inbox.
|
"""Send a message to another agent's inbox — sparingly.
|
||||||
|
|
||||||
The target's next ``inject_messages_filter`` pass (top of its next LLM
|
Inter-agent messages are surfaced at the top of the target's next
|
||||||
turn) drains the inbox and surfaces the message wrapped in
|
LLM turn. Use only when essential:
|
||||||
``<inter_agent_message>``. Messages to a finalized agent are dropped
|
|
||||||
silently by the bus (C13).
|
- Sharing a discovered finding/credential another agent needs.
|
||||||
|
- Asking a specialist a focused question.
|
||||||
|
- Coordinating who covers what (avoid overlap).
|
||||||
|
- Telling a child to wrap up or change course.
|
||||||
|
|
||||||
|
**Don't** use for routine "hello/status" pings, for context the
|
||||||
|
target already has (children inherit parent history), or when
|
||||||
|
parent/child completion via ``agent_finish`` already covers the
|
||||||
|
flow. Messages to a finalized agent are dropped.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_agent_id: Recipient's 8-char id.
|
||||||
|
message: The full message body. Be specific — include payloads,
|
||||||
|
URLs, or what you want them to do, not just headlines.
|
||||||
|
message_type: ``query`` (you want a reply), ``instruction``
|
||||||
|
(you're directing them), ``information`` (FYI, no reply
|
||||||
|
expected). Default ``information``.
|
||||||
|
priority: ``low`` / ``normal`` / ``high`` / ``urgent``.
|
||||||
"""
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
@@ -205,16 +235,31 @@ async def wait_for_message(
|
|||||||
reason: str = "Waiting for messages from other agents",
|
reason: str = "Waiting for messages from other agents",
|
||||||
timeout_seconds: int = 600,
|
timeout_seconds: int = 600,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Block this agent's turn until a message arrives or ``timeout_seconds``.
|
"""Pause this agent until a message lands in its inbox (or timeout).
|
||||||
|
|
||||||
Implementation polls ``bus.inboxes`` once per second. Cheaper than an
|
Use when you have nothing useful to do until a child/peer responds
|
||||||
asyncio.Event because the message bus already serializes through its
|
— typically after spawning subagents and you want to wait for
|
||||||
own lock — a missed wakeup on Event would be subtle to debug, while
|
their completion reports. The agent automatically resumes when any
|
||||||
polling is trivially correct.
|
message arrives.
|
||||||
|
|
||||||
|
**Critical caveats:**
|
||||||
|
|
||||||
|
- **Never** call this if you finished your own task and have **no**
|
||||||
|
child agents running — that's a permanent stall. Call
|
||||||
|
``finish_scan`` (root) or ``agent_finish`` (subagent) instead.
|
||||||
|
- If you're waiting on an agent that **isn't your child**, message
|
||||||
|
it first asking it to ping you when done — otherwise it has no
|
||||||
|
reason to send to your inbox and you'll wait the full timeout.
|
||||||
|
- Children update the parent automatically via ``agent_finish``
|
||||||
|
→ no extra coordination needed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reason: Human-readable note shown in graph snapshots while waiting.
|
reason: One-line note shown in graph snapshots while you're
|
||||||
timeout_seconds: Cap on the wait. 600s matches the legacy default.
|
waiting (helps a human or sibling agent debug who's stuck
|
||||||
|
on what).
|
||||||
|
timeout_seconds: Hard cap (default 600s). On timeout the tool
|
||||||
|
returns and you decide whether to keep working or wait
|
||||||
|
again.
|
||||||
"""
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
@@ -268,22 +313,44 @@ async def create_agent(
|
|||||||
inherit_context: bool = True,
|
inherit_context: bool = True,
|
||||||
skills: list[str] | None = None,
|
skills: list[str] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Spawn a child agent that runs in parallel via ``asyncio.create_task``.
|
"""Spawn a specialist child agent to run in parallel.
|
||||||
|
|
||||||
The child's ``Runner.run`` task handle is stored in ``bus.tasks[child_id]``
|
Decompose complex pentests by handing focused subtasks to dedicated
|
||||||
so a root-level cancel can cascade to descendants (C9). The child is
|
children. The child runs asynchronously — the parent continues
|
||||||
registered with the bus before the task starts so messages aimed at it
|
immediately and can ``wait_for_message`` later (or just keep
|
||||||
don't get dropped during the brief register→start window.
|
working in parallel). When the child calls ``agent_finish``, its
|
||||||
|
completion report lands in the parent's inbox.
|
||||||
|
|
||||||
|
**Before spawning, call ``view_agent_graph``** to confirm no
|
||||||
|
existing agent already covers this scope — duplicate specialists
|
||||||
|
waste turns and create coordination headaches.
|
||||||
|
|
||||||
|
**Specialization principles:**
|
||||||
|
|
||||||
|
- Most agents need at least one ``skill`` to be useful.
|
||||||
|
- Aim for **1-3 related skills** per agent. Up to 5 only when the
|
||||||
|
task genuinely spans them.
|
||||||
|
- One skill = most focused (e.g., XSS-only). Five skills = upper
|
||||||
|
bound.
|
||||||
|
- Match the ``name`` to the focus (``XSS Specialist``,
|
||||||
|
``SQLi Validator``, ``Auth Specialist``).
|
||||||
|
|
||||||
|
**When to spawn vs do it yourself:**
|
||||||
|
|
||||||
|
- Spawn when the subtask is large, parallelizable, or needs
|
||||||
|
different specialization than what you're already doing.
|
||||||
|
- Don't spawn for trivial one-shot probes — just run the tool
|
||||||
|
yourself.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Human-readable child name (also stored in ``bus.names``).
|
name: Human-readable child name (used in graph views and
|
||||||
task: The task description handed to the child agent.
|
``send_message_to_agent`` flows).
|
||||||
inherit_context: When True, the child receives a copy of the parent's
|
task: Specific objective. Be concrete — what to test, what
|
||||||
input items as background context, wrapped in
|
success looks like, any constraints.
|
||||||
``<inherited_context_from_parent>``. Default True.
|
inherit_context: Default ``True``. The child receives the
|
||||||
skills: Optional list of skill names the child should preload.
|
parent's input history as background; only set ``False``
|
||||||
|
when starting a clean-slate task.
|
||||||
Returns a JSON-encoded ``{"success": ..., "agent_id": ...}``.
|
skills: Comma-separated skill names. Max 5; prefer 1-3.
|
||||||
"""
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
@@ -412,15 +479,39 @@ async def agent_finish(
|
|||||||
report_to_parent: bool = True,
|
report_to_parent: bool = True,
|
||||||
final_recommendations: list[str] | None = None,
|
final_recommendations: list[str] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Subagent-only termination: post a completion report and signal the SDK.
|
"""Subagent termination — post a completion report to the parent.
|
||||||
|
|
||||||
Sets ``ctx.context['agent_finish_called'] = True`` so the on_agent_end
|
**Subagents only.** Root agents must call ``finish_scan`` instead;
|
||||||
hook records "completed" rather than "crashed". The SDK terminates the
|
this tool refuses to run for root agents. Calling this:
|
||||||
child's loop because every child is built with
|
|
||||||
``tool_use_behavior={"stop_at_tool_names": ["agent_finish"]}`` (C4).
|
|
||||||
|
|
||||||
Root agents must call ``finish_scan`` instead. This tool refuses to run
|
1. Marks the subagent as ``completed``.
|
||||||
when ``parent_id`` is None.
|
2. Posts a structured ``<agent_completion_report>`` to the
|
||||||
|
parent's inbox (when ``report_to_parent`` is true).
|
||||||
|
3. Stops this subagent's execution.
|
||||||
|
|
||||||
|
**Vulnerability findings must already be filed via
|
||||||
|
``create_vulnerability_report`` before calling this.** The
|
||||||
|
``findings`` field here is for narrative summary only — it does
|
||||||
|
not register vulns in the scan report.
|
||||||
|
|
||||||
|
Write the summary as if the parent has no idea what you were
|
||||||
|
doing: what did you test, what did you find/confirm/rule out,
|
||||||
|
what's still open.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
result_summary: What you accomplished and discovered. Concrete
|
||||||
|
and specific (URLs, parameters, payloads that worked).
|
||||||
|
findings: Optional bullet list of confirmed observations. For
|
||||||
|
credit-bearing vulnerabilities, file
|
||||||
|
``create_vulnerability_report`` first; this is for
|
||||||
|
narrative.
|
||||||
|
success: Whether the assigned subtask was completed
|
||||||
|
successfully. Default ``True``.
|
||||||
|
report_to_parent: Whether to deliver the completion report to
|
||||||
|
the parent's inbox. Default ``True``.
|
||||||
|
final_recommendations: Optional next-step suggestions for the
|
||||||
|
parent (e.g., "prioritize testing X", "spawn an agent to
|
||||||
|
cover Y").
|
||||||
"""
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
|
|||||||
@@ -67,20 +67,75 @@ async def browser_action(
|
|||||||
file_path: str | None = None,
|
file_path: str | None = None,
|
||||||
clear: bool = False,
|
clear: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Drive the sandboxed Playwright browser.
|
"""Drive the sandboxed Playwright browser (Chromium, headless).
|
||||||
|
|
||||||
|
The browser is **persistent** — state survives across calls and tabs
|
||||||
|
until you ``close``. Browser interaction must start with ``launch``
|
||||||
|
and end with ``close``. Multiple tabs are supported; the first tab
|
||||||
|
after ``launch`` is ``"tab_1"`` and new tabs are numbered
|
||||||
|
sequentially.
|
||||||
|
|
||||||
|
**Click coordinates** — derive them from the most recent screenshot.
|
||||||
|
Target the *center* of the element, not the edge. After clicking,
|
||||||
|
verify success against the next screenshot. Bad coordinates are the
|
||||||
|
most common reason clicks silently fail.
|
||||||
|
|
||||||
|
**JavaScript execution** (``execute_js``):
|
||||||
|
|
||||||
|
- Code runs in the page context with full DOM access.
|
||||||
|
- The **last evaluated expression is auto-returned** — do not use
|
||||||
|
``return`` (it breaks evaluation).
|
||||||
|
- For an object literal as the final expression, wrap in parentheses:
|
||||||
|
``({title: document.title, url: location.href})``.
|
||||||
|
- ``await`` is supported: ``await fetch(location.href).then(r => r.status)``.
|
||||||
|
- Variables from your tool context are NOT available — pass data
|
||||||
|
via the URL or DOM if you need to thread it through.
|
||||||
|
- The ``js_code`` parameter is executed as-is; no escaping needed,
|
||||||
|
single- or multi-line both work.
|
||||||
|
|
||||||
|
**Form filling** — click the field first, then ``type`` the text.
|
||||||
|
|
||||||
|
**Tabs** — actions affect the currently active tab unless ``tab_id``
|
||||||
|
is set. Always keep at least one tab open. Close tabs you don't need
|
||||||
|
with ``close_tab``, and ``close`` the browser when you're fully done.
|
||||||
|
|
||||||
|
**Concurrency** — the browser session can run alongside terminal /
|
||||||
|
python tool calls in subsequent turns; nothing in the browser is
|
||||||
|
serialized against other tools.
|
||||||
|
|
||||||
|
Special keys for ``press_key``: single chars ``a``-``z`` / ``0``-``9``,
|
||||||
|
``Enter`` / ``Escape`` / ``Tab`` / ``Space`` / ``ArrowLeft`` /
|
||||||
|
``ArrowRight`` / ``ArrowUp`` / ``ArrowDown``, modifiers ``Shift`` /
|
||||||
|
``Control`` / ``Alt`` / ``Meta``, function keys ``F1``-``F12``.
|
||||||
|
|
||||||
|
Returns: a JSON dict with ``screenshot`` (base64 PNG), ``url``,
|
||||||
|
``title``, ``viewport``, ``tab_id``, ``all_tabs``. Per-action extras:
|
||||||
|
``js_result`` for ``execute_js``, ``pdf_saved`` for ``save_pdf``,
|
||||||
|
``console_logs`` (≤50 KB / ≤200 most recent) for ``get_console_logs``,
|
||||||
|
``page_source`` (truncated to 100 KB) for ``view_source``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
action: The browser action to dispatch — see ``BrowserAction``
|
action: One of: ``launch``, ``goto``, ``click``, ``type``,
|
||||||
literal for the full set.
|
``scroll_down``, ``scroll_up``, ``back``, ``forward``,
|
||||||
url: Required for ``launch`` / ``goto`` / ``new_tab`` (with URL).
|
``new_tab``, ``switch_tab``, ``close_tab``, ``list_tabs``,
|
||||||
coordinate: ``"x,y"`` pixel target for click/hover/double_click.
|
``wait``, ``execute_js``, ``double_click``, ``hover``,
|
||||||
|
``press_key``, ``save_pdf``, ``get_console_logs``,
|
||||||
|
``view_source``, ``close``.
|
||||||
|
url: Required for ``launch`` / ``goto``; optional for
|
||||||
|
``new_tab``. Must include the protocol (e.g.
|
||||||
|
``https://...``, ``file://...``).
|
||||||
|
coordinate: ``"x,y"`` pixel target for ``click`` / ``double_click``
|
||||||
|
/ ``hover``. Format example: ``"432,321"``. Must be within
|
||||||
|
viewport.
|
||||||
text: Required for ``type``.
|
text: Required for ``type``.
|
||||||
tab_id: Optional explicit tab targeting; defaults to the active tab.
|
tab_id: Required for ``switch_tab`` / ``close_tab``; optional
|
||||||
|
elsewhere to target a specific tab.
|
||||||
js_code: Required for ``execute_js``.
|
js_code: Required for ``execute_js``.
|
||||||
duration: Seconds to wait for ``wait`` action.
|
duration: Seconds for ``wait`` (fractional OK, e.g. ``0.5``).
|
||||||
key: Required for ``press_key`` (e.g. ``"Enter"``, ``"Escape"``).
|
key: Required for ``press_key``.
|
||||||
file_path: Required for ``save_pdf``.
|
file_path: Required for ``save_pdf``.
|
||||||
clear: For ``type``, clears the field first.
|
clear: For ``get_console_logs``, clear logs after retrieval
|
||||||
|
(default False).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
|
|||||||
@@ -37,16 +37,34 @@ async def str_replace_editor(
|
|||||||
new_str: str | None = None,
|
new_str: str | None = None,
|
||||||
insert_line: int | None = None,
|
insert_line: int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""View, create, or edit a file in the sandbox.
|
"""View, create, or edit a file in the sandbox filesystem.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
- ``view`` — show file contents. Optionally restrict to a line range
|
||||||
|
via ``view_range`` (1-indexed; ``[start, -1]`` for "from start to
|
||||||
|
end of file").
|
||||||
|
- ``create`` — write a new file with ``file_text``. Use this for
|
||||||
|
exploit scripts, PoCs, helper modules, etc.
|
||||||
|
- ``str_replace`` — find ``old_str`` in the file and replace with
|
||||||
|
``new_str``. ``old_str`` must be unique in the file; include
|
||||||
|
enough surrounding context to make it so.
|
||||||
|
- ``insert`` — insert ``new_str`` after line ``insert_line``.
|
||||||
|
- ``undo_edit`` — revert the most recent edit to ``path``.
|
||||||
|
|
||||||
|
Multi-line ``new_str`` / ``old_str`` / ``file_text`` use real
|
||||||
|
newlines, not literal ``\\n``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
command: One of ``"view" | "create" | "str_replace" | "insert" |
|
command: ``view`` / ``create`` / ``str_replace`` / ``insert`` /
|
||||||
"undo_edit"``.
|
``undo_edit``.
|
||||||
path: File path. Relative paths are anchored at ``/workspace``.
|
path: File path. Relative paths anchor at ``/workspace``.
|
||||||
file_text: Required for ``create``.
|
file_text: Required for ``create``.
|
||||||
view_range: Optional ``[start, end]`` line range for ``view``.
|
view_range: Optional ``[start, end]`` (1-indexed) for ``view``.
|
||||||
old_str / new_str: Required for ``str_replace``.
|
old_str: Required for ``str_replace`` — must be unique in file.
|
||||||
insert_line: Required for ``insert``.
|
new_str: Required for ``str_replace`` and ``insert``.
|
||||||
|
insert_line: Required for ``insert``; new content goes AFTER
|
||||||
|
this line.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
@@ -73,9 +91,12 @@ async def list_files(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""List files and directories under a sandbox path.
|
"""List files and directories under a sandbox path.
|
||||||
|
|
||||||
|
Output is sorted alphabetically and capped at 500 entries to avoid
|
||||||
|
flooding the model with huge directory trees.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path: Directory path, relative paths anchored at ``/workspace``.
|
path: Directory path; relative paths anchor at ``/workspace``.
|
||||||
recursive: When True, walks subdirectories (capped at 500 entries).
|
recursive: When True, walks subdirectories.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
@@ -93,12 +114,17 @@ async def search_files(
|
|||||||
regex: str,
|
regex: str,
|
||||||
file_pattern: str = "*",
|
file_pattern: str = "*",
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Recursively grep files in the sandbox using ripgrep.
|
"""Recursively regex-search files in the sandbox using ripgrep.
|
||||||
|
|
||||||
|
Fast — uses ``rg`` under the hood. Walks subdirectories. Use this
|
||||||
|
for code-pattern hunts (``def\\s+authenticate``, ``API_KEY``,
|
||||||
|
secrets, etc.) when you don't already know the file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path: Root path to search; relative paths anchored at ``/workspace``.
|
path: Root path to search. Relative paths anchor at ``/workspace``.
|
||||||
regex: Pattern to match (passed straight to ``rg``).
|
regex: Pattern to match (PCRE-style; passed straight to ``rg``).
|
||||||
file_pattern: Glob filter (e.g. ``"*.py"``). Defaults to all files.
|
file_pattern: Glob filter (e.g. ``"*.py"``, ``"*.{js,ts}"``).
|
||||||
|
Defaults to all files.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
|
|||||||
@@ -79,16 +79,57 @@ async def finish_scan(
|
|||||||
technical_analysis: str,
|
technical_analysis: str,
|
||||||
recommendations: str,
|
recommendations: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Finalize the scan and persist the four executive summary sections.
|
"""Finalize the scan — persist the customer-facing report.
|
||||||
|
|
||||||
Only the root agent should call this. Subagents must use
|
**Root-agent only.** Subagents must call ``agent_finish`` from the
|
||||||
``agent_finish`` (from the multi-agent graph tools) instead.
|
multi-agent graph tools instead. Calling this finalizes everything:
|
||||||
|
|
||||||
|
1. Verifies you are the root agent.
|
||||||
|
2. Writes the four narrative sections to the scan record.
|
||||||
|
3. Marks the scan completed and stops execution.
|
||||||
|
|
||||||
|
**Pre-flight checklist:**
|
||||||
|
|
||||||
|
- All vulnerabilities you found are filed via
|
||||||
|
``create_vulnerability_report`` (un-reported findings are not
|
||||||
|
tracked and not credited).
|
||||||
|
- All subagents have terminated. If any are still ``running`` /
|
||||||
|
``stopping``, message them or use ``wait_for_message``.
|
||||||
|
- Don't double-report — one report per distinct vulnerability.
|
||||||
|
|
||||||
|
**Calling this multiple times overwrites the previous report.**
|
||||||
|
Make the single call comprehensive.
|
||||||
|
|
||||||
|
**Customer-facing report rules** (this output is rendered into the
|
||||||
|
final PDF the client sees):
|
||||||
|
|
||||||
|
- Never mention internal infrastructure: no local/absolute paths
|
||||||
|
(``/workspace/...``), no agent names, no sandbox/orchestrator/
|
||||||
|
tooling references, no system prompts, no model-internal errors.
|
||||||
|
- Tone: formal, third-person, objective, concise. This is a
|
||||||
|
consultant deliverable, not an engineering log.
|
||||||
|
- Each section has a specific role:
|
||||||
|
|
||||||
|
- ``executive_summary`` — for non-technical leadership. Risk
|
||||||
|
posture, business impact (data exposure / compliance /
|
||||||
|
reputation), notable criticals, overarching remediation
|
||||||
|
theme.
|
||||||
|
- ``methodology`` — frameworks followed (OWASP WSTG, PTES,
|
||||||
|
OSSTMM, NIST), engagement type (black/gray/white box), scope
|
||||||
|
and constraints, categories of testing performed. **No**
|
||||||
|
internal execution detail.
|
||||||
|
- ``technical_analysis`` — consolidated findings overview with
|
||||||
|
severity model and systemic root causes. Reference individual
|
||||||
|
vuln reports for repro steps; don't duplicate raw evidence.
|
||||||
|
- ``recommendations`` — prioritized actions grouped by urgency
|
||||||
|
(Immediate / Short-term / Medium-term), each with concrete
|
||||||
|
remediation steps. End with retest/validation guidance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
executive_summary: High-level scan outcome.
|
executive_summary: Business-level summary for leadership.
|
||||||
methodology: Approach taken.
|
methodology: Frameworks, scope, and approach.
|
||||||
technical_analysis: Findings detail across the engagement.
|
technical_analysis: Consolidated findings + systemic themes.
|
||||||
recommendations: Prioritized fix list.
|
recommendations: Prioritized, actionable remediation.
|
||||||
"""
|
"""
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
result = await asyncio.to_thread(
|
result = await asyncio.to_thread(
|
||||||
|
|||||||
@@ -425,11 +425,37 @@ async def create_note(
|
|||||||
category: str = "general",
|
category: str = "general",
|
||||||
tags: list[str] | None = None,
|
tags: list[str] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Create a note in the current run's notes store.
|
"""Document an observation, finding, methodology step, or research note.
|
||||||
|
|
||||||
Notes are persisted to ``run_dir/notes/notes.jsonl`` and (for the
|
Notes are your **shared run memory** — they're visible to every
|
||||||
``wiki`` category) rendered as Markdown to
|
agent in the same scan and persist to ``run_dir/notes/notes.jsonl``
|
||||||
``run_dir/wiki/<slug>.md``.
|
(replayable event log). Wiki-category notes are additionally
|
||||||
|
rendered as Markdown under ``run_dir/wiki/<slug>.md``.
|
||||||
|
|
||||||
|
For actionable tasks, use ``todo`` instead — notes are for capturing
|
||||||
|
information, todos are for tracking work.
|
||||||
|
|
||||||
|
Categories:
|
||||||
|
|
||||||
|
- ``general`` — default, anything that doesn't fit elsewhere.
|
||||||
|
- ``findings`` — confirmed vulnerabilities or weaknesses (write
|
||||||
|
these up promptly; you'll cite them when filing reports).
|
||||||
|
- ``methodology`` — what you tried, what worked, what didn't —
|
||||||
|
useful for the final scan report.
|
||||||
|
- ``questions`` — open questions / things to come back to.
|
||||||
|
- ``plan`` — multi-step plans you want to track.
|
||||||
|
- ``wiki`` — repository or target source maps shared across agents
|
||||||
|
in the same run. Use this for codebase architecture notes the
|
||||||
|
whole agent tree should see.
|
||||||
|
|
||||||
|
Tags are free-form (e.g. ``["sqli", "auth", "critical"]``) — useful
|
||||||
|
for later ``list_notes(tags=...)`` filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Short headline.
|
||||||
|
content: Full note body. Markdown is preserved.
|
||||||
|
category: One of the categories above. Default ``"general"``.
|
||||||
|
tags: Optional free-form tags.
|
||||||
"""
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
return _dump(
|
return _dump(
|
||||||
@@ -445,7 +471,24 @@ async def list_notes(
|
|||||||
search: str | None = None,
|
search: str | None = None,
|
||||||
include_content: bool = False,
|
include_content: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""List notes, optionally filtered by category / tags / substring."""
|
"""List existing notes — metadata-first by default.
|
||||||
|
|
||||||
|
Filters compose: passing ``category="findings"`` and
|
||||||
|
``tags=["sqli"]`` returns notes that are *both* in the findings
|
||||||
|
category AND have at least one of those tags.
|
||||||
|
|
||||||
|
By default each entry includes a ``content_preview`` (first 280
|
||||||
|
chars). Set ``include_content=True`` to get full bodies — useful
|
||||||
|
when you need to scan many notes; expensive in tokens for large
|
||||||
|
notes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
category: Filter by category.
|
||||||
|
tags: Filter to notes that have any of these tags.
|
||||||
|
search: Substring match against title and content.
|
||||||
|
include_content: When False (default) entries have a preview;
|
||||||
|
when True the full ``content`` is included.
|
||||||
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
return _dump(
|
return _dump(
|
||||||
await asyncio.to_thread(
|
await asyncio.to_thread(
|
||||||
@@ -460,7 +503,11 @@ async def list_notes(
|
|||||||
|
|
||||||
@strix_tool(timeout=30)
|
@strix_tool(timeout=30)
|
||||||
async def get_note(ctx: RunContextWrapper, note_id: str) -> str:
|
async def get_note(ctx: RunContextWrapper, note_id: str) -> str:
|
||||||
"""Fetch one note by its 5-char ID. Returns full content."""
|
"""Fetch one note by its 5-char ID. Returns the full content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
note_id: Note id from ``create_note`` or a ``list_notes`` entry.
|
||||||
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
return _dump(await asyncio.to_thread(_get_note_impl, note_id))
|
return _dump(await asyncio.to_thread(_get_note_impl, note_id))
|
||||||
|
|
||||||
@@ -473,7 +520,18 @@ async def update_note(
|
|||||||
content: str | None = None,
|
content: str | None = None,
|
||||||
tags: list[str] | None = None,
|
tags: list[str] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Update a note's title, content, or tags."""
|
"""Update a note's title, content, or tags.
|
||||||
|
|
||||||
|
Pass ``None`` for any field you want left unchanged. Replacing
|
||||||
|
``content`` is a full overwrite — to append, fetch first with
|
||||||
|
``get_note``, concat, and pass the result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
note_id: Target note's 5-char ID.
|
||||||
|
title: New title, or ``None`` to keep.
|
||||||
|
content: New content, or ``None`` to keep.
|
||||||
|
tags: New tags list, or ``None`` to keep.
|
||||||
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
return _dump(
|
return _dump(
|
||||||
await asyncio.to_thread(
|
await asyncio.to_thread(
|
||||||
@@ -488,6 +546,10 @@ async def update_note(
|
|||||||
|
|
||||||
@strix_tool(timeout=30)
|
@strix_tool(timeout=30)
|
||||||
async def delete_note(ctx: RunContextWrapper, note_id: str) -> str:
|
async def delete_note(ctx: RunContextWrapper, note_id: str) -> str:
|
||||||
"""Delete a note. For wiki notes, also removes the rendered Markdown file."""
|
"""Delete a note. For wiki notes, also removes the rendered Markdown file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
note_id: Note id to delete.
|
||||||
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
return _dump(await asyncio.to_thread(_delete_note_impl, note_id))
|
return _dump(await asyncio.to_thread(_delete_note_impl, note_id))
|
||||||
|
|||||||
+155
-20
@@ -50,15 +50,35 @@ async def list_requests(
|
|||||||
sort_order: SortOrder = "desc",
|
sort_order: SortOrder = "desc",
|
||||||
scope_id: str | None = None,
|
scope_id: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""List captured HTTP requests from the Caido proxy.
|
"""List captured HTTP requests from the Caido proxy with HTTPQL filtering.
|
||||||
|
|
||||||
|
Caido HTTPQL syntax (operators differ by field type):
|
||||||
|
|
||||||
|
- **Integer fields** (``resp.code``, ``req.port``, ``id``,
|
||||||
|
``roundtrip``) — ``eq``, ``gt``, ``gte``, ``lt``, ``lte``, ``ne``.
|
||||||
|
Examples: ``resp.code.eq:200``, ``resp.code.gte:400``,
|
||||||
|
``req.port.eq:443``.
|
||||||
|
- **Text/byte fields** (``req.method``, ``req.host``, ``req.path``,
|
||||||
|
``req.query``, ``req.ext``, ``req.raw``) — ``regex``, ``cont``
|
||||||
|
(substring), ``eq``. Examples: ``req.method.eq:"POST"``,
|
||||||
|
``req.path.cont:"/api/"``, ``req.host.regex:".*\\.example\\.com"``.
|
||||||
|
- **Date fields** (``req.created_at``) — ``gt``, ``lt`` with ISO
|
||||||
|
timestamps: ``req.created_at.gt:"2024-01-01T00:00:00Z"``.
|
||||||
|
- **Combine** with ``AND`` / ``OR``: ``req.method.eq:"POST" AND
|
||||||
|
resp.code.gte:400``.
|
||||||
|
- **Special**: ``source:intercept`` (only intercepted requests),
|
||||||
|
``preset:"name"``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
httpql_filter: Caido HTTPQL query (e.g. ``"resp.code:eq:500"``).
|
httpql_filter: Caido HTTPQL query.
|
||||||
start_page / end_page: Inclusive page range to return.
|
start_page: Starting page, 1-indexed.
|
||||||
page_size: Entries per page; default 50.
|
end_page: Ending page (inclusive).
|
||||||
sort_by: Field to sort by.
|
page_size: Entries per page (default 50).
|
||||||
sort_order: ``"asc"`` or ``"desc"``.
|
sort_by: ``timestamp`` / ``host`` / ``method`` / ``path`` /
|
||||||
scope_id: Restrict to a specific scope.
|
``status_code`` / ``response_time`` / ``response_size`` /
|
||||||
|
``source``.
|
||||||
|
sort_order: ``asc`` or ``desc``.
|
||||||
|
scope_id: Restrict to a scope (managed via ``scope_rules``).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
@@ -86,7 +106,31 @@ async def view_request(
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
page_size: int = 50,
|
page_size: int = 50,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""View a single captured request or its response, with optional regex highlight."""
|
"""View a captured request or its response, optionally regex-searched.
|
||||||
|
|
||||||
|
Two modes:
|
||||||
|
|
||||||
|
- **With** ``search_pattern`` (compact regex hits) — returns up to 20
|
||||||
|
matches with ``before`` / ``after`` context and position. Useful
|
||||||
|
for hunting reflected input, leaked URLs, hidden parameters.
|
||||||
|
- **Without** ``search_pattern`` (full content with pagination) —
|
||||||
|
returns the page of raw content plus ``has_more`` flag.
|
||||||
|
|
||||||
|
Common search patterns:
|
||||||
|
|
||||||
|
- API endpoints: ``/api/[a-zA-Z0-9._/-]+``
|
||||||
|
- URLs: ``https?://[^\\s<>"']+``
|
||||||
|
- Query parameters: ``[?&][a-zA-Z0-9_]+=([^&\\s<>"']+)``
|
||||||
|
- Specific input reflection: search for the value you submitted.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request_id: Request ID from ``list_requests``.
|
||||||
|
part: ``"request"`` or ``"response"``.
|
||||||
|
search_pattern: Optional regex; switches the response shape to
|
||||||
|
compact hits.
|
||||||
|
page: 1-indexed page number (only when no ``search_pattern``).
|
||||||
|
page_size: Lines per page.
|
||||||
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -116,12 +160,17 @@ async def send_request(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""Send an arbitrary HTTP request through the Caido proxy.
|
"""Send an arbitrary HTTP request through the Caido proxy.
|
||||||
|
|
||||||
|
Use this for one-off probes (test endpoints, reach external APIs).
|
||||||
|
For modifying-and-replaying a request you've already captured, use
|
||||||
|
``repeat_request`` instead — it inherits the original headers /
|
||||||
|
cookies / auth and only patches the fields you specify.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
method: ``"GET"``, ``"POST"``, etc.
|
method: ``"GET"`` / ``"POST"`` / ``"PUT"`` / ``"DELETE"`` / etc.
|
||||||
url: Full URL.
|
url: Full URL with protocol.
|
||||||
headers: Optional header dict.
|
headers: Optional header dict.
|
||||||
body: Optional body string.
|
body: Optional request body string.
|
||||||
timeout: Per-request timeout in seconds.
|
timeout: Per-request timeout in seconds (default 30).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
@@ -147,7 +196,31 @@ async def repeat_request(
|
|||||||
request_id: str,
|
request_id: str,
|
||||||
modifications: dict[str, Any] | None = None,
|
modifications: dict[str, Any] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Repeat a captured request, optionally applying field modifications."""
|
"""Repeat a captured request, optionally patching individual fields.
|
||||||
|
|
||||||
|
The standard pentesting workflow with this tool:
|
||||||
|
|
||||||
|
1. ``browser_action`` (or live target traffic) → request gets
|
||||||
|
captured by Caido.
|
||||||
|
2. ``list_requests`` → find the request ID you want to manipulate.
|
||||||
|
3. ``repeat_request`` → send a modified version (auth-bypass test,
|
||||||
|
payload injection, parameter tampering).
|
||||||
|
|
||||||
|
Mirrors the manual "browse → capture → modify → test" flow used in
|
||||||
|
real pentesting. Inherits everything from the original request
|
||||||
|
(headers, cookies, auth, method, URL) and overlays only the fields
|
||||||
|
you specify in ``modifications``.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request_id: ID of the original request (from ``list_requests``).
|
||||||
|
modifications: Patch dict. Recognized keys:
|
||||||
|
|
||||||
|
- ``url`` — replace the URL.
|
||||||
|
- ``params`` — dict of query-string keys to add/update.
|
||||||
|
- ``headers`` — dict of headers to add/update.
|
||||||
|
- ``body`` — replace the body string entirely.
|
||||||
|
- ``cookies`` — dict of cookies to add/update.
|
||||||
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -169,7 +242,44 @@ async def scope_rules(
|
|||||||
scope_id: str | None = None,
|
scope_id: str | None = None,
|
||||||
scope_name: str | None = None,
|
scope_name: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""CRUD on Caido scope rules (allow/deny lists)."""
|
"""CRUD on Caido scope rules (allow/deny patterns).
|
||||||
|
|
||||||
|
Scopes filter which traffic Caido tools see. Use them to focus on a
|
||||||
|
target, exclude noisy assets (CDNs, static files), or define a
|
||||||
|
bug-bounty allowlist.
|
||||||
|
|
||||||
|
Pattern semantics:
|
||||||
|
|
||||||
|
- Glob wildcards: ``*`` (any), ``?`` (single), ``[abc]`` (one of),
|
||||||
|
``[a-z]`` (range), ``[^abc]`` (none of).
|
||||||
|
- **Empty allowlist = allow all domains.**
|
||||||
|
- **Denylist always overrides allowlist.**
|
||||||
|
|
||||||
|
Common denylist for noisy static assets:
|
||||||
|
``["*.gif", "*.jpg", "*.png", "*.css", "*.js", "*.ico", "*.svg",
|
||||||
|
"*woff*", "*.ttf"]``.
|
||||||
|
|
||||||
|
Each scope has a unique id usable as ``scope_id`` in
|
||||||
|
``list_requests`` / ``list_sitemap`` / ``view_request``.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action:
|
||||||
|
|
||||||
|
- ``list`` — return all scopes.
|
||||||
|
- ``get`` — single scope by ``scope_id`` (or all when
|
||||||
|
omitted).
|
||||||
|
- ``create`` — needs ``scope_name``, optionally
|
||||||
|
``allowlist`` / ``denylist``.
|
||||||
|
- ``update`` — needs ``scope_id`` + ``scope_name``;
|
||||||
|
allowlist / denylist replace the previous values.
|
||||||
|
- ``delete`` — needs ``scope_id``.
|
||||||
|
|
||||||
|
allowlist: Domain patterns to include (e.g.
|
||||||
|
``["*.example.com", "api.test.com"]``).
|
||||||
|
denylist: Patterns to exclude.
|
||||||
|
scope_id: Required for ``get`` / ``update`` / ``delete``.
|
||||||
|
scope_name: Required for ``create`` / ``update``.
|
||||||
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -193,13 +303,30 @@ async def list_sitemap(
|
|||||||
depth: SitemapDepth = "DIRECT",
|
depth: SitemapDepth = "DIRECT",
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""List Caido sitemap entries (proxied URL tree).
|
"""View the hierarchical sitemap of discovered attack surface.
|
||||||
|
|
||||||
|
The sitemap is built from proxied traffic — every URL the target
|
||||||
|
served gets indexed into a tree of domains → directories → request
|
||||||
|
leaves. Use it to understand application structure and find
|
||||||
|
interesting endpoints, hidden directories, parameter variations.
|
||||||
|
|
||||||
|
Entry kinds you'll encounter:
|
||||||
|
|
||||||
|
- ``DOMAIN`` — root host (``example.com``).
|
||||||
|
- ``DIRECTORY`` — path segment (``/api/``, ``/admin/``).
|
||||||
|
- ``REQUEST`` — a specific endpoint.
|
||||||
|
- ``REQUEST_BODY`` — POST/PUT body variations (different payloads
|
||||||
|
seen at the same URL).
|
||||||
|
- ``REQUEST_QUERY`` — query-string variations.
|
||||||
|
|
||||||
|
Each entry has ``hasDescendants`` — set ``parent_id`` to that
|
||||||
|
entry's id to drill in. Pages return 30 entries each.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
scope_id: Restrict to a scope.
|
scope_id: Filter to a specific scope.
|
||||||
parent_id: Drill into a specific subtree.
|
parent_id: Drill into a subtree. ``None`` returns root domains.
|
||||||
depth: ``"DIRECT"`` (direct children only) or ``"ALL"`` (recursive).
|
depth: ``"DIRECT"`` (immediate children) or ``"ALL"`` (recursive).
|
||||||
page: 1-indexed page number.
|
page: 1-indexed page (30 entries/page).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
@@ -217,7 +344,15 @@ async def list_sitemap(
|
|||||||
|
|
||||||
@strix_tool(timeout=60)
|
@strix_tool(timeout=60)
|
||||||
async def view_sitemap_entry(ctx: RunContextWrapper, entry_id: str) -> str:
|
async def view_sitemap_entry(ctx: RunContextWrapper, entry_id: str) -> str:
|
||||||
"""Fetch a single sitemap entry's metadata + linked requests."""
|
"""Examine one sitemap entry — full metadata + every related request.
|
||||||
|
|
||||||
|
Use this after ``list_sitemap`` identifies an interesting directory
|
||||||
|
or endpoint to see all the requests captured under it (methods,
|
||||||
|
paths, response codes, timing).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry_id: Sitemap entry id from ``list_sitemap``.
|
||||||
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(ctx, "view_sitemap_entry", {"entry_id": entry_id}),
|
await post_to_sandbox(ctx, "view_sitemap_entry", {"entry_id": entry_id}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,13 +31,53 @@ async def python_action(
|
|||||||
timeout: int = 30,
|
timeout: int = 30,
|
||||||
session_id: str | None = None,
|
session_id: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Manage / execute code in a long-lived sandboxed IPython session.
|
"""Run Python code in a long-lived IPython session — preferred for any
|
||||||
|
Python work (payloads, exploit scripts, HTTP automation, log analysis,
|
||||||
|
crypto, data processing).
|
||||||
|
|
||||||
|
Pick this over ``terminal_execute`` whenever the work is Python.
|
||||||
|
Don't wrap Python in bash heredocs, ``python -c`` one-liners, or
|
||||||
|
interactive REPL sessions in the terminal — the structured,
|
||||||
|
persistent, debuggable execution lives here.
|
||||||
|
|
||||||
|
Sessions are **persistent** — variables, imports, and function
|
||||||
|
definitions survive between ``execute`` calls within the same
|
||||||
|
``session_id``. Each session has its own isolated namespace; multiple
|
||||||
|
sessions can run concurrently. Sessions stay alive until explicitly
|
||||||
|
``close``-d.
|
||||||
|
|
||||||
|
Caido proxy helpers are pre-imported into every session, so you can
|
||||||
|
correlate captured HTTP requests with custom analysis without any
|
||||||
|
setup: ``list_requests`` / ``view_request`` / ``send_request`` /
|
||||||
|
``repeat_request`` / ``scope_rules`` / ``list_sitemap`` /
|
||||||
|
``view_sitemap_entry`` are all available as bare names.
|
||||||
|
|
||||||
|
For large payload sprays / fuzzing loops, encapsulate the entire
|
||||||
|
loop inside a single ``python_action`` ``execute`` call (e.g.,
|
||||||
|
asyncio + aiohttp). Don't issue one tool call per payload — that
|
||||||
|
burns turns and is dramatically slower.
|
||||||
|
|
||||||
|
Code execution notes:
|
||||||
|
|
||||||
|
- Both expressions and statements are supported. Expressions auto-
|
||||||
|
return their result; ``print`` output is captured to stdout.
|
||||||
|
- IPython magics work: ``%pip install ...``, ``%time``, ``%whos``,
|
||||||
|
``%%writefile``, etc.
|
||||||
|
- Use real newlines in multi-line ``code``, not literal ``\\n``.
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
|
||||||
|
1. ``new_session`` (always first per ``session_id``) — optionally
|
||||||
|
pass ``code`` for an initial setup snippet (imports, helpers).
|
||||||
|
2. ``execute`` — run code. Variables persist across calls.
|
||||||
|
3. ``close`` — terminate the session and free memory.
|
||||||
|
4. ``list_sessions`` — inspect what's currently alive.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
action: ``"new_session"`` to spin one up, ``"execute"`` to run code,
|
action: ``"new_session"`` / ``"execute"`` / ``"close"`` /
|
||||||
``"close"`` to terminate, ``"list_sessions"`` to inspect.
|
``"list_sessions"``.
|
||||||
code: Required for ``execute`` (and optional for ``new_session``
|
code: Required for ``execute``; optional initial code for
|
||||||
to run a setup snippet immediately).
|
``new_session``.
|
||||||
timeout: Per-call execution budget in seconds. Default 30.
|
timeout: Per-call execution budget in seconds. Default 30.
|
||||||
session_id: Required for ``execute`` / ``close``. Optional for
|
session_id: Required for ``execute`` / ``close``. Optional for
|
||||||
``new_session`` (auto-generated when omitted).
|
``new_session`` (auto-generated when omitted).
|
||||||
|
|||||||
@@ -333,11 +333,78 @@ async def create_vulnerability_report(
|
|||||||
cwe: str | None = None,
|
cwe: str | None = None,
|
||||||
code_locations: str | None = None,
|
code_locations: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""File a vulnerability report against the active scan.
|
"""File a vulnerability report — one report per fully-verified finding.
|
||||||
|
|
||||||
The report is dedup-checked against existing reports (LLM-based
|
**When to file**: you have a concrete vulnerability with a working
|
||||||
similarity); if it's a near-duplicate, the call returns a
|
proof-of-concept and you're 100% sure it's a real issue.
|
||||||
``duplicate_of`` pointer instead of creating a new entry.
|
|
||||||
|
**When NOT to file**:
|
||||||
|
|
||||||
|
- General security observations without a specific vulnerability.
|
||||||
|
- Suspicions you haven't confirmed with a PoC.
|
||||||
|
- Tracking multiple vulnerabilities at once — one report per vuln.
|
||||||
|
- Re-reporting something you (or another agent) already filed.
|
||||||
|
|
||||||
|
Automatic LLM-based **deduplication** rejects reports that describe
|
||||||
|
the same root cause on the same asset as an existing report. If you
|
||||||
|
get a ``duplicate_of`` response, do NOT retry — move on to other
|
||||||
|
areas.
|
||||||
|
|
||||||
|
**Customer-facing report rules** (the report is PDF-rendered for
|
||||||
|
delivery):
|
||||||
|
|
||||||
|
- No internal/system details: never mention paths like
|
||||||
|
``/workspace``, internal tools, agents, sandboxes, models, system
|
||||||
|
prompts, internal errors / stack traces, or tester environment.
|
||||||
|
- Tone: formal, objective, third-person, vendor-neutral, concise.
|
||||||
|
- Standard finding structure: Overview → Severity & CVSS →
|
||||||
|
Affected assets → Technical details → PoC (steps + code) →
|
||||||
|
Impact → Remediation → Evidence (in technical_analysis).
|
||||||
|
- Numbered steps allowed only in PoC and Remediation sections.
|
||||||
|
- Avoid hedging language; be precise and non-vague.
|
||||||
|
|
||||||
|
**White-box requirement**: when source is available, you MUST
|
||||||
|
populate ``code_locations`` with nested XML including
|
||||||
|
``fix_before`` / ``fix_after`` for proposed fixes. The fix_before
|
||||||
|
must be a verbatim copy of source at the specified line range — it's
|
||||||
|
used as a literal GitHub/GitLab PR suggestion block.
|
||||||
|
|
||||||
|
**CVSS breakdown** is required as nested XML with all 8 metrics
|
||||||
|
(each a single uppercase letter):
|
||||||
|
|
||||||
|
- ``attack_vector``: ``N`` (Network), ``A`` (Adjacent), ``L``
|
||||||
|
(Local), ``P`` (Physical)
|
||||||
|
- ``attack_complexity``: ``L`` / ``H``
|
||||||
|
- ``privileges_required``: ``N`` / ``L`` / ``H``
|
||||||
|
- ``user_interaction``: ``N`` / ``R``
|
||||||
|
- ``scope``: ``U`` (Unchanged) / ``C`` (Changed)
|
||||||
|
- ``confidentiality`` / ``integrity`` / ``availability``: ``N`` /
|
||||||
|
``L`` / ``H``
|
||||||
|
|
||||||
|
**CVE / CWE rules**: pass the bare ID only (``CVE-2024-1234``,
|
||||||
|
``CWE-89``) — no name, no parenthetical. Be 100% certain; if
|
||||||
|
unsure, omit. Always prefer the most specific child CWE over a
|
||||||
|
broad parent (CWE-89 not CWE-74; CWE-78 not CWE-77).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Specific finding title (e.g.
|
||||||
|
``"SQL Injection in /api/users login parameter"``). Don't
|
||||||
|
include the CVE number in the title.
|
||||||
|
description: How the vuln was discovered + what it is.
|
||||||
|
impact: What an attacker achieves; business risk; data at risk.
|
||||||
|
target: Affected URL / domain / repository.
|
||||||
|
technical_analysis: The mechanism and root cause.
|
||||||
|
poc_description: Step-by-step reproduction.
|
||||||
|
poc_script_code: Working PoC (Python preferred).
|
||||||
|
remediation_steps: Specific, actionable fix.
|
||||||
|
cvss_breakdown: 8-metric XML block per the format above.
|
||||||
|
endpoint: API path / Git path (e.g. ``/api/login``).
|
||||||
|
method: HTTP method when relevant.
|
||||||
|
cve: ``CVE-YYYY-NNNNN`` if certain, else omit.
|
||||||
|
cwe: ``CWE-NNN`` (most specific child) if certain, else omit.
|
||||||
|
code_locations: Required for white-box findings; nested XML
|
||||||
|
list with ``file``, ``start_line``, ``end_line``,
|
||||||
|
``snippet``, ``fix_before``, ``fix_after``.
|
||||||
"""
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
result = await asyncio.to_thread(
|
result = await asyncio.to_thread(
|
||||||
|
|||||||
@@ -31,16 +31,66 @@ async def terminal_execute(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""Run a shell command in the sandboxed Kali tmux session.
|
"""Run a shell command in the sandboxed Kali tmux session.
|
||||||
|
|
||||||
|
The session is **persistent** — environment variables, current
|
||||||
|
directory, and running processes carry across calls keyed by
|
||||||
|
``terminal_id`` (default: ``"default"``). Use distinct ids to run
|
||||||
|
multiple concurrent sessions.
|
||||||
|
|
||||||
|
When to use this vs ``python_action``:
|
||||||
|
|
||||||
|
- Shell work: CLI tools (nmap, sqlmap, ffuf, nuclei), package
|
||||||
|
managers, file/system commands, services, process control. Use
|
||||||
|
``terminal_execute``.
|
||||||
|
- Python code, data processing, HTTP automation, iterative scripting:
|
||||||
|
use ``python_action`` instead — it's more structured and easier to
|
||||||
|
debug. Don't run embedded Python via ``python -c`` or heredocs
|
||||||
|
here.
|
||||||
|
|
||||||
|
Avoid long pipelines and complex bash one-liners; prefer multiple
|
||||||
|
simple calls for clarity and debugging. For multi-step shell work,
|
||||||
|
separate tool calls beat ``&& ; |``-chained commands.
|
||||||
|
|
||||||
|
Long-running commands:
|
||||||
|
|
||||||
|
- Commands are **never** killed automatically — they keep running
|
||||||
|
after the timeout fires.
|
||||||
|
- ``timeout`` (max 60s, capped) only controls how long to wait for
|
||||||
|
output before returning. On timeout the call returns
|
||||||
|
``status="running"``; on completion ``status="completed"``.
|
||||||
|
- For daemons / very long jobs, append ``&`` to background.
|
||||||
|
- Use an **empty command** to poll for new output from a running
|
||||||
|
process (the call waits ``timeout`` seconds collecting output).
|
||||||
|
- Use ``C-c`` / ``C-d`` / ``C-z`` to interrupt — special keys work
|
||||||
|
automatically without setting ``is_input``.
|
||||||
|
|
||||||
|
Interactive processes:
|
||||||
|
|
||||||
|
- ``is_input=True`` sends the command as input to a running foreground
|
||||||
|
process (REPL prompts, ``apt install`` y/n, etc.).
|
||||||
|
- ``no_enter=True`` sends keystrokes without a trailing newline —
|
||||||
|
useful for vim navigation (``gg``, ``5j``, ``i``), passwords, or
|
||||||
|
multi-step keybindings.
|
||||||
|
|
||||||
|
Special key support (tmux key names): ``C-c``, ``C-d``, ``Up``,
|
||||||
|
``Down``, ``F1``-``F12``, ``Enter``, ``Escape``, ``Tab``, ``Space``,
|
||||||
|
``BSpace``, ``M-f`` (alt), ``S-Tab`` (shift), and combinations like
|
||||||
|
``C-S-key``. Note: ``BSpace`` not ``Backspace``, ``Escape`` not
|
||||||
|
``Esc``.
|
||||||
|
|
||||||
|
Working directory is tracked across calls and returned in the
|
||||||
|
response. Large outputs are auto-truncated.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
command: Shell command (or input for an interactive prompt when
|
command: Shell command, special key (``C-c``), or empty string
|
||||||
``is_input=True``).
|
to poll a running process.
|
||||||
is_input: Treat ``command`` as input to a running foreground process
|
is_input: Treat ``command`` as input to a running foreground
|
||||||
(e.g., feeding y/n to ``apt install``).
|
process. Special keys auto-detect; you only need this for
|
||||||
timeout: Seconds to wait before returning partial output. Defaults
|
regular text input.
|
||||||
to the in-container manager's policy.
|
timeout: Seconds to wait before returning partial output. Capped
|
||||||
terminal_id: Persistent session selector. Defaults to ``"default"``.
|
at 60s. Defaults to 30s.
|
||||||
|
terminal_id: Persistent session selector. Use distinct ids for
|
||||||
|
concurrent sessions.
|
||||||
no_enter: When True, sends keystrokes without a trailing return.
|
no_enter: When True, sends keystrokes without a trailing return.
|
||||||
Useful for sending raw ANSI control sequences.
|
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return _dump(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
|
|||||||
@@ -9,15 +9,28 @@ from strix.tools._decorator import strix_tool
|
|||||||
|
|
||||||
@strix_tool(timeout=10)
|
@strix_tool(timeout=10)
|
||||||
async def think(thought: str) -> str:
|
async def think(thought: str) -> str:
|
||||||
"""Record a private chain-of-thought note without taking any action.
|
"""Record a private chain-of-thought note. No side effects, no new info.
|
||||||
|
|
||||||
The "think" tool is the planning escape hatch for situations where a
|
Use ``think`` when you need a dedicated space to reason before acting —
|
||||||
message-without-tool-call would otherwise halt the run (per the
|
not as an output channel. It's particularly valuable for:
|
||||||
interactive-mode tool-call requirement). The thought itself is
|
|
||||||
recorded but produces no side effects.
|
- **Tool output analysis** — carefully processing the output of a
|
||||||
|
previous tool call before deciding the next step.
|
||||||
|
- **Policy-heavy environments** — when you need to follow detailed
|
||||||
|
guidelines (engagement scope, auth boundaries) and verify compliance
|
||||||
|
before each action.
|
||||||
|
- **Sequential decision making** — when each action builds on previous
|
||||||
|
ones and mistakes are costly (e.g., destructive operations,
|
||||||
|
irreversible auth changes).
|
||||||
|
- **Multi-step exploit planning** — breaking down a complex chain into
|
||||||
|
manageable steps and tracking what's been confirmed vs. assumed.
|
||||||
|
|
||||||
|
Structure your thought to be useful: current state, what you've
|
||||||
|
confirmed, your next planned actions, risk assessment. Don't use
|
||||||
|
``think`` to chat — use it to plan.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
thought: The agent's reasoning to record. Must be non-empty.
|
thought: The reasoning to record. Must be non-empty.
|
||||||
"""
|
"""
|
||||||
if not thought or not thought.strip():
|
if not thought or not thought.strip():
|
||||||
return json.dumps({"success": False, "message": "Thought cannot be empty"})
|
return json.dumps({"success": False, "message": "Thought cannot be empty"})
|
||||||
|
|||||||
@@ -211,7 +211,33 @@ async def create_todo(
|
|||||||
priority: str = "normal",
|
priority: str = "normal",
|
||||||
todos: str | None = None,
|
todos: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Create one or many todos for the current agent."""
|
"""Create one or many todos for the current agent.
|
||||||
|
|
||||||
|
Each agent (including subagents) has its **own private todo list** —
|
||||||
|
your todos don't leak to other agents and vice versa.
|
||||||
|
|
||||||
|
When to use:
|
||||||
|
|
||||||
|
- Planning multi-step assessments with parallel workstreams.
|
||||||
|
- Tracking work you'll come back to later.
|
||||||
|
- Breaking down complex scopes (per-endpoint, per-target, per-vuln-class).
|
||||||
|
|
||||||
|
When NOT to use:
|
||||||
|
|
||||||
|
- Simple linear workflows where progress is obvious.
|
||||||
|
- Single quick task — just do it.
|
||||||
|
|
||||||
|
Batch related todos in one call via the ``todos`` bulk parameter
|
||||||
|
rather than firing many ``create_todo`` calls.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Short, actionable title (e.g., "Test /api/admin for IDOR").
|
||||||
|
description: Optional details / context for the single todo.
|
||||||
|
priority: ``"low"`` / ``"normal"`` / ``"high"`` / ``"critical"``.
|
||||||
|
todos: Bulk create — either JSON array of
|
||||||
|
``{"title": "...", "description": "...", "priority": "..."}``
|
||||||
|
objects, or a newline-separated bullet list (``- item\\n- item``).
|
||||||
|
"""
|
||||||
agent_id = _agent_id_from(ctx)
|
agent_id = _agent_id_from(ctx)
|
||||||
try:
|
try:
|
||||||
default_priority = _normalize_priority(priority)
|
default_priority = _normalize_priority(priority)
|
||||||
@@ -271,7 +297,16 @@ async def list_todos(
|
|||||||
status: str | None = None,
|
status: str | None = None,
|
||||||
priority: str | None = None,
|
priority: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""List the current agent's todos, sorted by status then priority."""
|
"""List the current agent's todos, sorted by status then priority.
|
||||||
|
|
||||||
|
Sort order: status (done → in_progress → pending), then priority
|
||||||
|
within each status (critical → high → normal → low).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status: Filter — ``"pending"`` / ``"in_progress"`` / ``"done"``.
|
||||||
|
priority: Filter — ``"low"`` / ``"normal"`` / ``"high"`` /
|
||||||
|
``"critical"``.
|
||||||
|
"""
|
||||||
agent_id = _agent_id_from(ctx)
|
agent_id = _agent_id_from(ctx)
|
||||||
try:
|
try:
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
@@ -333,7 +368,19 @@ async def update_todo(
|
|||||||
status: str | None = None,
|
status: str | None = None,
|
||||||
updates: str | None = None,
|
updates: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Update one or many todos."""
|
"""Update one or many todos. Prefer the bulk form for multiple updates.
|
||||||
|
|
||||||
|
For toggling status only, use the dedicated ``mark_todo_done`` /
|
||||||
|
``mark_todo_pending`` tools — they're simpler and accept bulk
|
||||||
|
``todo_ids``.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: Single-todo target.
|
||||||
|
title / description / priority / status: New values for the
|
||||||
|
single todo. Omit to leave unchanged.
|
||||||
|
updates: Bulk form — JSON array like
|
||||||
|
``[{"todo_id": "abc", "status": "done"}, ...]``.
|
||||||
|
"""
|
||||||
agent_id = _agent_id_from(ctx)
|
agent_id = _agent_id_from(ctx)
|
||||||
try:
|
try:
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
@@ -437,7 +484,13 @@ async def mark_todo_done(
|
|||||||
todo_id: str | None = None,
|
todo_id: str | None = None,
|
||||||
todo_ids: str | None = None,
|
todo_ids: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Mark one (``todo_id``) or many (``todo_ids``) todos as done."""
|
"""Mark one or many todos as done.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: Single todo's ID.
|
||||||
|
todo_ids: Bulk form — JSON array, comma-separated string, or
|
||||||
|
single ID. Combinable with ``todo_id`` for one-off plus bulk.
|
||||||
|
"""
|
||||||
return _mark(
|
return _mark(
|
||||||
agent_id=_agent_id_from(ctx),
|
agent_id=_agent_id_from(ctx),
|
||||||
todo_id=todo_id,
|
todo_id=todo_id,
|
||||||
@@ -452,7 +505,12 @@ async def mark_todo_pending(
|
|||||||
todo_id: str | None = None,
|
todo_id: str | None = None,
|
||||||
todo_ids: str | None = None,
|
todo_ids: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Mark one (``todo_id``) or many (``todo_ids``) todos as pending."""
|
"""Reset one or many todos to pending (e.g., to retry a failed task).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: Single todo's ID.
|
||||||
|
todo_ids: Bulk form — JSON array, comma-separated, or single ID.
|
||||||
|
"""
|
||||||
return _mark(
|
return _mark(
|
||||||
agent_id=_agent_id_from(ctx),
|
agent_id=_agent_id_from(ctx),
|
||||||
todo_id=todo_id,
|
todo_id=todo_id,
|
||||||
@@ -467,7 +525,12 @@ async def delete_todo(
|
|||||||
todo_id: str | None = None,
|
todo_id: str | None = None,
|
||||||
todo_ids: str | None = None,
|
todo_ids: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Delete one (``todo_id``) or many (``todo_ids``) todos."""
|
"""Delete one or many todos. Removes them entirely (no soft-delete).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: Single todo's ID.
|
||||||
|
todo_ids: Bulk form — JSON array, comma-separated, or single ID.
|
||||||
|
"""
|
||||||
agent_id = _agent_id_from(ctx)
|
agent_id = _agent_id_from(ctx)
|
||||||
try:
|
try:
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
|
|||||||
@@ -86,11 +86,40 @@ def _do_search(query: str) -> dict[str, Any]:
|
|||||||
# budget so the round-trip + JSON decode doesn't push us over.
|
# budget so the round-trip + JSON decode doesn't push us over.
|
||||||
@strix_tool(timeout=330)
|
@strix_tool(timeout=330)
|
||||||
async def web_search(ctx: RunContextWrapper, query: str) -> str:
|
async def web_search(ctx: RunContextWrapper, query: str) -> str:
|
||||||
"""Search the web with Perplexity, scoped to security-relevant content.
|
"""Real-time web search via Perplexity — your primary research tool.
|
||||||
|
|
||||||
|
Use it liberally for anything that's not in your training data:
|
||||||
|
|
||||||
|
- Current CVEs, advisories, and 0-days for a specific
|
||||||
|
service/version (``OpenSSH 9.6 RCE``, ``Jenkins 2.401.3 auth
|
||||||
|
bypass``).
|
||||||
|
- Latest WAF / EDR bypass techniques (``Cloudflare WAF SQLi
|
||||||
|
bypass 2025``, ``CrowdStrike Falcon evasion``).
|
||||||
|
- Tool documentation, flag references, payload galleries.
|
||||||
|
- Target reconnaissance / OSINT (company tech stack, leaked
|
||||||
|
credentials, exposed assets).
|
||||||
|
- Cloud-provider misconfiguration patterns
|
||||||
|
(Azure/AWS/GCP-specific attack paths).
|
||||||
|
- Bug-bounty writeups and security research papers.
|
||||||
|
- Compliance frameworks and CWE/CVSS guidance.
|
||||||
|
- Picking the right Python lib / Kali tool for a job (``best 2025
|
||||||
|
lib for JWT alg-confusion``).
|
||||||
|
- When stuck — looking up the exact error message, ``Access
|
||||||
|
denied`` quirks, kernel-specific local-privesc exploits.
|
||||||
|
|
||||||
|
Be specific: include version numbers, error messages, target
|
||||||
|
technology, and the exact problem you're stuck on. The more context
|
||||||
|
in the query, the more actionable the answer. Vague queries get
|
||||||
|
generic answers.
|
||||||
|
|
||||||
|
A security-focused system prompt biases responses toward CVEs,
|
||||||
|
exploits, Kali-compatible tooling, and concrete code/command
|
||||||
|
examples.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: The search query. A security-focused system prompt biases
|
query: The search query — a full sentence with version numbers,
|
||||||
results toward CVEs, exploits, and Kali-compatible commands.
|
target tech, and the specific question. Treat it like a
|
||||||
|
ticket title for a senior security engineer.
|
||||||
"""
|
"""
|
||||||
del ctx
|
del ctx
|
||||||
result = await asyncio.to_thread(_do_search, query)
|
result = await asyncio.to_thread(_do_search, query)
|
||||||
|
|||||||
Reference in New Issue
Block a user