Files
strix/strix/skills/tooling/python.md
T
0xallamandClaude Opus 4.7 9d7f754b59 feat(tools): python_action — stateless Python execution with proxy helpers
Restores the legacy persistent-IPython tool's *ergonomics* (proxy
helpers pre-bound, structured stdout/stderr/error returns) without the
in-container daemon: each call ships ``strix.tools.proxy._calls`` source
into ``/tmp`` alongside a per-call driver, runs ``python3 -u`` against
it, and parses a sentinel-delimited JSON payload back from stdout. The
driver fetches its own guest token from Caido at ``localhost:48080``
and binds ``list_requests`` / ``view_request`` / ``send_request`` /
``repeat_request`` / ``scope_rules`` to that client; user code runs
inside an ``async def`` wrapper so top-level ``await`` works.

The proxy SDK call sequences live in one file —
``strix/tools/proxy/_calls.py`` — and are reused by both the host-side
``@function_tool`` wrappers (which add JSON serialization for the LLM)
and the in-container kernel (which exposes the bare async functions).
No code duplication; the helper logic itself is host-shipped, so
tweaking the proxy helpers does not require an image rebuild.

Image: a single ``pip install caido-sdk-client`` line so the driver's
``import caido_sdk_client`` resolves. Skill ``tooling/python`` is
always-loaded alongside ``tooling/agent_browser``.

Trade-off accepted: state does not persist across calls (no kernel).
For multi-step workflows the agent combines into one ``code`` block or
writes a script to ``/workspace/scratch/`` and runs via
``exec_command``. If a workflow surfaces that genuinely needs
persistence, the same tool surface migrates to a kernel-backed
executor without changing the LLM contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:58:53 -07:00

4.0 KiB

name, description
name description
python python_action — execute Python in the sandbox with Caido proxy helpers (list_requests, view_request, send_request, repeat_request, scope_rules) pre-bound as awaitables. Stateless per call; persistence via files.

python_action — when and how

Use python_action for any Python-side work: payload encoding/decoding, parsing/transforming captured HTTP traffic, crypto operations, custom exploit scripts, log/JSON analysis. Use exec_command for shell tools (nmap, sqlmap, ffuf, agent-browser, package managers, daemons).

Do not wrap Python in bash heredocs, python3 -c one-liners, or echo | python3 chains via exec_commandpython_action exists so structured output replaces fragile stdout parsing.

What's pre-bound (no imports needed)

All proxy helpers are async — call them with await:

  • list_requests(httpql_filter=, first=50, after=, sort_by=, sort_order=, scope_id=) → cursor-paginated SDK Connection. Iterate connection.edges; each edge has .cursor and .node.request / .node.response.
  • view_request(request_id, part="request") → SDK request object. .request.raw and .response.raw are bytes.
  • send_request(method, url, headers=None, body="") → dict with status, error, elapsed_ms, response_raw (bytes or None), session_id.
  • repeat_request(request_id, modifications={...}) → same shape. modifications keys: url / params / headers / body / cookies.
  • scope_rules(action, allowlist=, denylist=, scope_id=, scope_name=) — same actions as the host-side tool (list/get/create/ update/delete).

Top-level await works — the body is wrapped in an async function for you. print() to emit visible output; the last expression is not auto-shown.

Stateless model + how to keep state

Each python_action call is a fresh process: variables, imports, and definitions do not survive. To carry state across steps:

  • Combine into one call when the workflow is short — write the full multi-step routine as one code block.
  • Persist to disk for longer-lived state. /workspace/scratch/ is pentester-writable and survives across calls within a scan.
  • Build a script with apply_patch to /workspace/scratch/<name>.py and run it via exec_command python3 ... when you need a file the agent can iterate on.

Examples

Hunt SQLi candidates by inspecting captured traffic

# All POSTs that look interesting
posts = await list_requests(
    httpql_filter='req.method.eq:"POST" AND req.path.cont:"/api/"',
    first=50,
)
candidates = []
for edge in posts.edges:
    body = await view_request(edge.node.request.id, part="request")
    raw = body.request.raw.decode("utf-8", errors="replace")
    if "id=" in raw or "user=" in raw:
        candidates.append(edge.node.request.id)

print(f"{len(candidates)} candidates")
print(candidates[:10])
result = await repeat_request(
    "req_abc123",
    modifications={
        "params": {"id": "1' OR '1'='1"},
        "cookies": {"session": "ATTACKER_TOKEN"},
    },
)
print(result["status"], result["elapsed_ms"], "ms")
if result["response_raw"]:
    print(result["response_raw"].decode("utf-8", errors="replace")[:500])

Decode/encode payloads

import base64, urllib.parse, hashlib

token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWxpY2UifQ.sig"
header_b64, payload_b64, _ = token.split(".")
print(base64.urlsafe_b64decode(payload_b64 + "=="))

Iterate an exploit by writing to scratch

When iterating, prefer writing the script to disk so you can edit-and-rerun without re-sending the whole code each call:

# 1. Use apply_patch to create /workspace/scratch/exploit.py
# 2. exec_command: python3 /workspace/scratch/exploit.py
# 3. Edit + re-run; repeat until working

For one-shot crypto/encoding work or a single proxy-data analysis, python_action is the cleaner choice.