Skip to content

Investigate an incident with SecurityContext

The point of SecurityContext is that you reason in domains, not vendors. The whole investigation below never says "Splunk" or "Okta" or "CrowdStrike" — it says logs, identity, threat-intel, endpoint, actions. Swap a real vendor in by injecting a provider (SecurityContext(logs=SplunkLogs())); the investigation code does not change.

The notebook walks a suspected account compromise across six domains — search the logs, score the user's risk, enrich an indicator, pull the host timeline — then proposes containment and gates it through policy before acting. One investigation, six domains, zero vendor names.

Runs offline on the bundled reference providers.

Run it: python examples/notebook_82_investigate_with_ctx.py

See also: SecurityContext.

Source

#!/usr/bin/env python3
# Copyright 2026 Tulip Labs
# SPDX-License-Identifier: Apache-2.0

"""Notebook 82: investigate an incident with SecurityContext — no vendor names.

The point of `SecurityContext` is that you reason in *domains*, not vendors. The
whole investigation below never says "Splunk" or "Okta" or "CrowdStrike" — it
says logs, identity, threat-intel, endpoint, actions. Swap a real vendor in by
injecting a provider (`SecurityContext(logs=SplunkLogs())`); the investigation
code does not change.

Runs offline on the bundled reference providers.

Run:
    python examples/notebook_82_investigate_with_ctx.py
"""

from __future__ import annotations

import asyncio

from tulip.control import Action
from tulip.security import SecurityContext, VerificationResult


async def main() -> int:
    ctx = SecurityContext()  # zero config — offline reference providers
    print("Investigating a suspected account compromise (vendor-agnostic)\n")

    # 1. Logs: what happened around the alert.
    logs = await ctx.logs.search("failed login spike", window="6h")
    print(f"1. logs.search -> {logs['count']} event(s) from {logs['source']}")

    # 2. Identity: is the user risky?
    risk = await ctx.identity.risk("[email protected]")
    print(f"2. identity.risk -> {risk['risk']} (impossible_travel={risk['impossible_travel']})")

    # 3. Threat intel: enrich an observed indicator.
    intel = await ctx.threat_intel.enrich("198.51.100.23")
    print(
        f"3. threat_intel.enrich -> {intel.get('classification', intel.get('verdict', 'see record'))}"
    )

    # 4. Endpoint: pull the host's recent forensics.
    host = await ctx.endpoint.get_host("WS-0142")
    print(f"4. endpoint.get_host -> timeline from {host.get('source', 'edr')}")

    # 5. Action: propose containment — but gate it through policy first.
    #    (High-confidence verification stands in for a verified finding.)
    verdict = VerificationResult(survives=True, confidence=0.93, evidence_quality=0.93)
    decision = ctx.actions.request_approval(
        Action(name="disable_user", asset="[email protected]", environment="production"),
        verdict=verdict,
    )
    print(f"5. actions.request_approval -> {decision.outcome.upper()}: {decision.reason}")

    print(
        "\nOne investigation, six domains, zero vendor names. The same code runs "
        "against real Splunk / Okta / CrowdStrike once you inject those providers."
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(asyncio.run(main()))