Engagement & RoE API

HTTP reference for Rules of Engagement, the kill switch, and in-run approval decisions that keep a pentest fail-closed.

All paths are relative to https://api.aleex-rank.ai/api/v2 and authenticate with X-API-Key: rk_... (see REST API). These endpoints bind a pentest to an active Rules of Engagement (RoE), stop it immediately with the kill switch, and decide in-run approvals. The model is described in Engagement, RoE & kill switch.

The orchestrator is fail-closed: it will not start a pentest that has no active RoE, and a version cannot be activated unless every pentest asset falls inside that scope. Widening the RoE of a running pentest requires an approved scope_expansion.

Rules of Engagement

GET    /pentests/{id}/roe
POST   /pentests/{id}/roe
PUT    /pentests/{id}/roe

POST creates a new version. PUT replaces the current draft. activate is a control flag on write — it is not stored on the object. Send activate: true to make the version active once validation passes.

At least one of allowed_cidrs or allowed_domains must be a non-empty list. Public IPv4 entries in allowed_cidrs must be /16 or more specific; public IPv6 must be /32 or more specific. Private ranges are exempt from that floor.

FieldTypeRequiredNotes
allowed_cidrsstring[]ConditionalCIDRs in scope; at least one allow list must be non-empty
allowed_domainsstring[]ConditionalDomains in scope
denied_cidrsstring[]NoAlways excluded, even if also allowed
denied_domainsstring[]NoAlways excluded
allowed_techniquesstring[]NoATT&CK ids Txxxx or Txxxx.xxx
forbidden_techniquesstring[]NoATT&CK ids the engagement must not use
source_ipsstring[]NoSource addresses the orchestrator may use
time_windowsobject[]NoEach {start, end, days}; start/end are HH:MM
timezonestringConditionalIANA zone; required when time_windows is set
asset_criticalityobjectNoMap of asset → critical | high | medium | low | info
max_rpsintNoRequest-rate ceiling
max_concurrencyintNoParallel-action ceiling
authorization_refstringYesTicket, memo or contract id that authorizes the test
escalation_contactsobject[]YesNon-empty. type is email, slack or webhook; value is the target
requires_approval_forstring[]Noshell, script, destructive, exploit, scope_expansion, low_scope_confidence
auto_approveboolNoWhen true, listed classes may auto-approve — never scope_expansion
activateboolNoWrite-only control flag; omitted on reads

days on a window is all or an array of mon…sun.

{
  "allowed_cidrs": ["203.0.113.0/24"],
  "allowed_domains": ["acme.example.com"],
  "denied_cidrs": ["203.0.113.10/32"],
  "denied_domains": ["admin.acme.example.com"],
  "allowed_techniques": ["T1190", "T1059.001"],
  "forbidden_techniques": ["T1498", "T1565"],
  "source_ips": ["198.51.100.20"],
  "time_windows": [
    {"start": "09:00", "end": "18:00", "days": ["mon", "tue", "wed", "thu", "fri"]}
  ],
  "timezone": "Europe/Madrid",
  "asset_criticality": {"acme.example.com": "high", "203.0.113.10": "critical"},
  "max_rps": 10,
  "max_concurrency": 4,
  "authorization_ref": "ROE-2026-014",
  "escalation_contacts": [
    {"type": "email", "value": "soc@acme.example"},
    {"type": "slack", "value": "#security-escalations"}
  ],
  "requires_approval_for": ["shell", "destructive", "scope_expansion"],
  "auto_approve": false,
  "activate": true
}

POST returns 201 with the stored version (no activate field). Activation that would leave an asset outside scope, or a public allow-list that is too broad, fails with 400.

GET returns the active RoE, the version history, and the approval classes this pentest may request:

{
  "success": true,
  "data": {
    "active": {
      "version": 2,
      "allowed_cidrs": ["203.0.113.0/24"],
      "allowed_domains": ["acme.example.com"],
      "denied_cidrs": ["203.0.113.10/32"],
      "denied_domains": ["admin.acme.example.com"],
      "authorization_ref": "ROE-2026-014",
      "escalation_contacts": [
        {"type": "email", "value": "soc@acme.example"}
      ],
      "requires_approval_for": ["shell", "destructive", "scope_expansion"],
      "auto_approve": false,
      "timezone": "Europe/Madrid",
      "activated_at": "2026-03-02 09:00:00"
    },
    "versions": [
      {"version": 1, "status": "superseded", "created_at": "2026-03-01 18:12:00"},
      {"version": 2, "status": "active", "created_at": "2026-03-02 09:00:00"}
    ],
    "available_approval_classes": [
      {"id": "shell", "description": "Interactive or one-shot shell commands (shell_tool)"},
      {"id": "script", "description": "Interpreter scripts (script_tool)"},
      {"id": "destructive", "description": "Shell or script whose payload looks destructive"},
      {"id": "exploit", "description": "Active exploitation tools (sqlmap, commix, hydra, metasploit, and similar)"},
      {"id": "scope_expansion", "description": "Widening the active RoE of a pentest that is already running"},
      {"id": "low_scope_confidence", "description": "An action whose target could not be confidently matched to the authorised scope"}
    ]
  }
}

Class ids match GET /catalogs/approval-classes. Adding CIDRs or domains to a running pentest is a widening: submit the new version, then approve the resulting scope_expansion request before it can become active.

Kill switch

POST /pentests/{id}/kill

Stops the engagement immediately on the REST control plane. reason is optional, max 500 characters:

{"reason": "Traffic observed against an out-of-scope host"}
{
  "success": true,
  "data": {
    "message": "Kill requested",
    "pentest_id": 15,
    "kill_requested": true,
    "kill_requested_at": "2026-03-02T11:20:00+01:00",
    "reason": "Traffic observed against an out-of-scope host"
  }
}

This is not POST https://aleex.aleex-rank.ai/pentest/{id}/cancel, which asks the streaming backend to cancel an in-flight run. Use /kill when the RoE is breached or you need a hard stop regardless of stream state.

Approvals

GET  /pentests/{id}/approvals
POST /pentests/{id}/approvals/{approvalId}/decide

GET lists in-run approval requests. Filter with ?status=pending (also approved, denied).

{
  "success": true,
  "data": {
    "approvals": [
      {
        "id": 88,
        "pentest_id": 15,
        "action_class": "scope_expansion",
        "action_summary": "Add 198.51.100.0/24 to allowed_cidrs",
        "target": "198.51.100.0/24",
        "status": "pending",
        "requested_at": "2026-03-02 11:04:00",
        "auto_approved": false
      }
    ],
    "total": 1
  }
}

Decide with decision approve or deny; reason is optional:

{"decision": "approve", "reason": "Change ticket CHG-441 covers the extra subnet"}
{
  "success": true,
  "data": {
    "id": 88,
    "action_class": "scope_expansion",
    "status": "approved",
    "decided_at": "2026-03-02 11:12:00"
  }
}

scope_expansion is never auto-approved, even when auto_approve is true.

Where to go next