Rules of Engagement & kill switch

Activate a fail-closed RoE, list in-run approvals, and halt a pentest with the kill switch.

What you’ll build

The fail-closed control plane around a pentest: you create and activate a Rules of Engagement that covers the assets, list pending approvals and decide them, and fire the kill switch (distinct from cancel). Without an active RoE the orchestrator will not start (no_active_roe).

The CLI does not create RoE — use the SDK, REST, or the web platform.

Prerequisites

  • A Rank account and an API token (see Authentication).
  • A pentest whose assets fall inside the allow-list you declare (or let the script create one).
pip install rank-sdk
export RANK_API_KEY=rk_...

Steps

The approval-class vocabulary is GET /catalogs/approval-classes (client.catalogs.list_approval_classes()). Window warnings and control_stop arrive on the live stream — see Streaming.

Run it

Save the following as engagement_control.py, set RANK_API_KEY, then run python engagement_control.py. Kill is commented out so the script is safe to run repeatedly.

"""Engagement control — RoE, approvals, and the kill switch.

What this script does:
  1. Creates a web pentest against example.com (or reuses RANK_PENTEST_ID).
  2. Activates a Rules of Engagement that covers the assets.
  3. Lists pending approvals and approves each one.
  4. Prints how kill differs from cancel (kill is commented out).

Without an active RoE the orchestrator will not start.

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

Optional environment variables:
    RANK_PENTEST_ID  Existing pentest whose assets fall inside example.com.
    RANK_AUTH_REF    RoE authorization_ref (default: ENG-2026-0042).
    RANK_ESCALATION  Escalation email (default: security@example.com).
"""

from __future__ import annotations

import os

import rank

PENTEST_ID = int(os.environ.get("RANK_PENTEST_ID", "0"))
AUTH_REF = os.environ.get("RANK_AUTH_REF", "ENG-2026-0042")
ESCALATION = os.environ.get("RANK_ESCALATION", "security@example.com")


def main() -> None:
    with rank.Rank() as client:
        pentest_id = PENTEST_ID
        if pentest_id <= 0:
            pentest = client.pentests.create(
                name="Engagement control",
                type="web",
                mode="automatic",
                assets=[
                    {
                        "asset_type": "url",
                        "asset_value": "https://example.com",
                        "is_primary": True,
                    },
                ],
            )
            pentest_id = pentest.id
            print(f"Created pentest #{pentest_id}")
        else:
            print(f"Using pentest #{pentest_id}")

        current = client.pentests.roe.retrieve(pentest_id)
        if current.active:
            print(f"Active RoE v{current.active.version} status={current.active.status}")
        else:
            print("No active RoE — the orchestrator will not start.")

        roe = client.pentests.roe.create(
            pentest_id,
            allowed_domains=["example.com"],
            timezone="Europe/Madrid",
            max_rps=10,
            max_concurrency=4,
            requires_approval_for=["exploit"],
            authorization_ref=AUTH_REF,
            escalation_contacts=[{"type": "email", "value": ESCALATION}],
            activate=True,
        )
        print(f"Activated RoE v{roe.version} status={roe.status}")

        pending = client.pentests.approvals.list(pentest_id, status="pending")
        print(f"{pending.total} pending approval(s)")
        for a in pending.approvals:
            print(f"  [{a.id}] {a.action_class} target={a.target}")
            decided = client.pentests.approvals.decide(
                pentest_id, a.id,
                decision="approve",
                reason="In scope for this window",
            )
            print(f"    -> {decided.status}")

        print("cancel() asks a running stream to stop (Go).")
        print("kill() is the terminal switch (PHP); evidence is preserved.")
        print("Uncomment the next line to actually halt the run.")
        # killed = client.pentests.kill(pentest_id, reason="Out of authorized window")
        # print(killed.kill_requested, killed.reason)


if __name__ == "__main__":
    try:
        main()
    except rank.AuthenticationError:
        print("ERROR: invalid or missing API key. Set RANK_API_KEY.")
    except rank.NotFoundError:
        print("ERROR: pentest not found. Set RANK_PENTEST_ID or omit it to create one.")
    except rank.APIError as exc:
        print(f"API error ({exc.status_code}): {exc.message}")

Where to go next