back to scripts

l3-escalation.py

python 203 lines secrets redacted

Encodes the issues only a human (L3) can decide — with the affected system, the why, and the proposed change.

Note Live script from my home-lab server. Tokens, IDs, phone numbers and other secrets have been replaced with placeholders like <WHATSAPP_GROUP_ID> — everything else is the real, running code.
#!/usr/bin/env python3
import datetime
import hashlib
import json
import os
import subprocess
import sys

HANDOFF_FILE = "/var/lib/lanky-cyber-handoff/active.json"
STATE_DIR = "/var/lib/lanky-l3-escalations"
QUEUE_FILE = os.path.join(STATE_DIR, "active.json")
SENT_FILE = os.path.join(STATE_DIR, "last-sent.sha256")
LEDGER = "/home/lanky/reports/l3-escalations.jsonl"
GROUP_ID = os.environ.get("LANKY_ALERT_GROUP_ID", "<WHATSAPP_GROUP_ID>")


PLANS = {
    "gateway.trusted_proxies_missing": {
        "title": "OpenClaw reverse-proxy trust",
        "affected": "OpenClaw gateway and Control UI",
        "why_l3": "L2 cannot change authentication trust boundaries or guess which proxy IPs are trusted.",
        "change": (
            "If the Control UI is local-only, document that decision and make no configuration change. "
            "If it is reverse-proxied, identify the exact proxy source IP and add only that IP to "
            "gateway.trustedProxies, then restart and re-audit OpenClaw."
        ),
        "risk": "Trusting the wrong address can permit spoofed proxy headers; leaving it unset is unsafe if a proxy exposes the UI.",
        "reply": "L3 216bf5ccfc65a0a4 local-only OR L3 216bf5ccfc65a0a4 proxy <exact IP>",
    },
    "security.trust_model.multi_user_heuristic": {
        "title": "OpenClaw multi-user trust boundary",
        "affected": "WhatsApp Service Desk and credentials available to OpenClaw",
        "why_l3": "L2 cannot decide whether WhatsApp group members are mutually trusted or redesign identity isolation.",
        "change": (
            "Confirm every allowlisted group member is trusted as the same operator. If not, separate users "
            "onto distinct gateways and credentials; until separated, enable full sandboxing and restrict runtime, "
            "filesystem, and web tools to the minimum required."
        ),
        "risk": "A user inside the allowed group may gain access to another user's tools, data, or credentials.",
        "reply": "L3 390b1f5c97b413f7 trusted-users OR L3 390b1f5c97b413f7 separate-users",
    },
    "plugins.installs_unpinned_npm_specs": {
        "title": "Pin the OpenClaw Brave plugin",
        "affected": "OpenClaw Brave Search plugin",
        "why_l3": "Changing plugin supply-chain configuration and restarting the gateway is outside L2 authority.",
        "change": (
            "Replace the unpinned install record @openclaw/brave-plugin with the currently installed exact version "
            "@openclaw/brave-plugin@2026.6.1, restart OpenClaw, and verify the warning clears."
        ),
        "risk": "Low operational risk. Pinning prevents an unintended future plugin version from being selected.",
        "reply": "approve L3 db96ae6b643964b0",
    },
}


def load_json(path: str) -> dict:
    try:
        with open(path, encoding="utf-8") as handle:
            value = json.load(handle)
            return value if isinstance(value, dict) else {}
    except (OSError, ValueError):
        return {}


def write_json(path: str, value: dict) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    temporary = path + ".tmp"
    with open(temporary, "w", encoding="utf-8") as handle:
        json.dump(value, handle, indent=2, ensure_ascii=True)
        handle.write("\n")
    os.replace(temporary, path)


def send(message: str) -> bool:
    result = subprocess.run(
        [
            "sudo", "-u", "lanky", "-H", "openclaw", "message", "send",
            "--channel", "whatsapp", "--account", "default",
            "--target", GROUP_ID, "--message", message,
        ],
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        timeout=120,
        check=False,
    )
    return result.returncode == 0


def build_queue() -> dict:
    handoffs = load_json(HANDOFF_FILE)
    previous = load_json(QUEUE_FILE)
    now = datetime.datetime.now(datetime.timezone.utc).isoformat()
    queue = {}
    for finding_id, handoff in handoffs.items():
        if handoff.get("status") != "escalated_l3":
            continue
        plan = PLANS.get(handoff.get("detail"))
        if not plan:
            # Historical authentication counters are not change requests by themselves.
            continue
        prior = previous.get(finding_id, {})
        queue[finding_id] = {
            "id": finding_id,
            "status": prior.get("status", "pending"),
            "created": prior.get("created", now),
            "source": handoff.get("source", "unknown"),
            "owner": handoff.get("target", "unknown"),
            **plan,
        }
        if prior.get("response"):
            queue[finding_id]["response"] = prior["response"]
            queue[finding_id]["responded"] = prior.get("responded")
    write_json(QUEUE_FILE, queue)
    return queue


def pending(queue: dict) -> list[dict]:
    return [item for item in queue.values() if item.get("status") == "pending"]


def format_item(item: dict, compact: bool = False) -> str:
    if compact:
        return (
            f"L3 {item['id']}: {item['title']}\n"
            f"Needed: {item['change']}\n"
            f"Reply exactly: {item['reply']}"
        )
    return (
        f"L3 CHANGE REQUIRES YOUR DECISION\n"
        f"ID: {item['id']}\n"
        f"Issue: {item['title']}\n"
        f"Affected: {item['affected']}\n"
        f"Why L2 stopped: {item['why_l3']}\n"
        f"Proposed fix: {item['change']}\n"
        f"Risk: {item['risk']}\n"
        f"Reply exactly: {item['reply']}"
    )


def notify(queue: dict, force: bool = False) -> int:
    items = pending(queue)
    if not items:
        return 0
    content = "\n\n".join(format_item(item) for item in items)
    digest = hashlib.sha256(content.encode()).hexdigest()
    try:
        previous = open(SENT_FILE, encoding="ascii").read().strip()
    except OSError:
        previous = ""
    if force or digest != previous:
        if not send(content):
            print("Failed to send L3 escalation to WhatsApp.", file=sys.stderr)
            return 1
        os.makedirs(STATE_DIR, exist_ok=True)
        with open(SENT_FILE, "w", encoding="ascii") as handle:
            handle.write(digest + "\n")
    return 0


def record_response(original: str, queue: dict) -> int:
    lowered = original.lower()
    finding_id = next((item_id for item_id in queue if item_id.lower() in lowered), None)
    if not finding_id:
        print("I could not match that response to a pending L3 ID.")
        print("Ask: show pending L3 changes")
        return 0
    item = queue[finding_id]
    item["status"] = "approved" if lowered.startswith("approve l3") else "decision_received"
    item["response"] = original
    item["responded"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
    write_json(QUEUE_FILE, queue)
    os.makedirs(os.path.dirname(LEDGER), exist_ok=True)
    with open(LEDGER, "a", encoding="utf-8") as handle:
        handle.write(json.dumps(item, ensure_ascii=True) + "\n")
    try:
        os.unlink(SENT_FILE)
    except OSError:
        pass
    print(f"Recorded your L3 response for {finding_id}: {original}")
    print("The Agent Manager will now track it as an authorized L3 change until implementation and verification.")
    return 0


def main() -> int:
    command = sys.argv[1].lower() if len(sys.argv) > 1 else "sync"
    queue = build_queue()
    if command == "list":
        items = pending(queue)
        if not items:
            print("There are no pending L3 changes.")
            return 0
        print("\n\n".join(format_item(item, compact=True) for item in items))
        return 0
    if command == "respond":
        return record_response(" ".join(sys.argv[2:]).strip(), queue)
    if command == "notify":
        return notify(queue, force="--force" in sys.argv[2:])
    return notify(queue)


if __name__ == "__main__":
    raise SystemExit(main())

back to scripts