Run your first automated pentest

Launch an autonomous web pentest with an active Rules of Engagement, stream it end to end, and email a sealed report.

What you’ll build

A complete automatic pentest against a single web target: you create it, activate a Rules of Engagement that covers the asset, let Rank auto-assign every default agent, stream the agents working in real time, and finish with a sealed report in your inbox. Automatic mode runs every phase end to end and processes the findings inside the same stream, so there is nothing to orchestrate by hand.

Without an active RoE the orchestrator will not start (no_active_roe).

Prerequisites

  • A Rank account and an API token (see Authentication).
  • The Python SDK or CLI installed.

Steps

The same flow works from the CLI, the Python SDK, or the raw REST API. Pick a tab.

Run it

Save the following as first_automated_pentest.py, set RANK_API_KEY, then run python first_automated_pentest.py.

"""Run your first automated pentest end to end with the Rank SDK.

What this script does:
  1. Creates a chat session so the run has conversation context.
  2. Creates an automatic web pentest against a single URL asset.
  3. Activates a Rules of Engagement that covers the target.
  4. Auto-assigns the default agents for every phase.
  5. Streams the execution, printing the assistant's answer and a compact
     activity log of agent events as they happen.
  6. Marks the pentest as finished and lists the stored vulnerabilities.
  7. Emails a sealed report (PDF + HTML, technical audience) to the configured recipient.

In automatic mode the agents run every phase end to end and process the
findings inside the same stream, so by the time the `complete` event arrives
the vulnerabilities are already stored.

Run:
    pip install rank-sdk
    export RANK_API_KEY=rk_...
    python first_automated_pentest.py

Optional environment variables:
    RANK_TARGET_URL    Target URL to scan (default: https://example.com).
    RANK_REPORT_EMAIL  Recipient for the sealed report (default: you@example.com).
    RANK_AUTH_REF      RoE authorization_ref (default: ENG-2026-0042).
"""

from __future__ import annotations

import os
from urllib.parse import urlparse

import rank

TARGET_URL = os.environ.get("RANK_TARGET_URL", "https://example.com")
REPORT_EMAIL = os.environ.get("RANK_REPORT_EMAIL", "you@example.com")
AUTH_REF = os.environ.get("RANK_AUTH_REF", "ENG-2026-0042")


def main() -> None:
    # The client reads RANK_API_KEY from the environment automatically.
    with rank.Rank() as client:
        chat = client.chats.create(nombre=f"Automated pentest - {TARGET_URL}")

        pentest = client.pentests.create(
            name="First automated pentest",
            url=TARGET_URL,
            type="web",
            mode="automatic",
            assets=[
                {"asset_type": "url", "asset_value": TARGET_URL, "is_primary": True},
            ],
        )
        print(f"Created pentest #{pentest.id} (status: {pentest.status})")

        host = urlparse(TARGET_URL).hostname or "example.com"
        roe = client.pentests.roe.create(
            pentest.id,
            allowed_domains=[host],
            authorization_ref=AUTH_REF,
            escalation_contacts=[{"type": "email", "value": REPORT_EMAIL}],
            activate=True,
        )
        print(f"RoE v{roe.version} status={roe.status}")

        # Automatic pentests auto-assign every default agent on first access.
        defaults = client.pentests.agents.default(pentest.id)
        if defaults.auto_assigned:
            print(f"Auto-assigned {defaults.assigned_count} agents across all phases")

        # Stream the run. Only `content` events form the assistant's answer;
        # `agent_event` items are live activity meant for a timeline view.
        print("\n--- Live execution ---\n")
        with client.ai.chat.stream(
            user_prompt="Start the pentest on the configured targets",
            pentest_id=pentest.id,
            mode="automatic",
            chat_id=chat.id,
        ) as stream:
            for event in stream:
                if event.type == "content":
                    print(event.content, end="", flush=True)
                elif event.type == "phase_start":
                    print(f"\n=== Phase {event.metadata.get('phase_id')} ===")
                elif event.is_agent_event:
                    ev = event.agent_event
                    if ev is not None and ev.event_type == rank.AgentEvent.TOOL_CALL:
                        print(f"\n  [tool] {ev.data.get('tool_name')}")
                elif event.type == "error":
                    print(f"\nERROR: {event.error}")
                elif event.type == "complete":
                    stored = event.metadata.get("vulnerabilities_stored", 0)
                    print(f"\n\nExecution complete - {stored} vulnerabilities stored")

        # Finalize the pentest and review the findings.
        finished = client.pentests.finish(pentest.id)
        print(f"\n{finished.message}")

        vulns = client.pentests.vulnerabilities.list(pentest.id)
        print(f"\nVulnerabilities ({len(vulns.items)} on this page):")
        for v in vulns.items:
            print(f"  [{v.id}] {v.severity:>8}  {v.vulnerability}  ({v.status})")

        # The pentest must be completed before a report can be generated.
        report = client.pentests.generate_report(
            pentest.id,
            recipient_email=REPORT_EMAIL,
            extended=1,
            formats=["pdf", "html"],
            audience="technical",
        )
        print(f"\n{report.message}")


if __name__ == "__main__":
    try:
        main()
    except rank.AuthenticationError:
        print("ERROR: invalid or missing API key. Set RANK_API_KEY.")
    except rank.APIError as exc:
        print(f"API error ({exc.status_code}): {exc.message}")

Next, try the guided pentest recipe to drive the run phase by phase, the Rules of Engagement recipe for kill and approvals, or the streaming protocol.