Security¶
tulip.security is the largest module in the SDK and the one the product is
positioned on. It covers three separable jobs:
- Red-teaming an agent — send adversarial probes at a target and report what got through.
- Grounding a finding — refuse to assert what the evidence does not support, and say so explicitly rather than guessing.
- Building a SOC agent — tools that talk to a SIEM, an EDR, a scanner, a threat-intel feed, and AWS, plus playbooks that sequence them.
The admission gate also lives under this package for historical reasons. Import
it from tulip.control instead — that is the domain-neutral
surface and the path that will keep working.
For the concepts, start with The control layer.
Running a job¶
The three entry points. Each takes a Target and returns a report; none of
them needs an agent instance.
red_team
async
¶
red_team(target: Target, *, suite: str = 'owasp-asi', probes: Sequence[Probe] | None = None, thresholds: GSARThresholds | None = None) -> list[GroundedFinding]
Red-team a target AI: run adversarial probes, return grounded findings.
Runs each probe in probes (default: the named suite) against
target and grounds its outcome. A probe whose attack landed yields a
:class:~tulip.security.findings.Evidence; an inconclusive one yields an
:class:~tulip.security.grounded.Abstention. Both are returned, in probe
order, so the caller has a complete, auditable record of what was asserted
and what was declined.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
Target
|
The AI system under assessment. |
required |
suite
|
str
|
Named probe suite to run when |
'owasp-asi'
|
probes
|
Sequence[Probe] | None
|
Explicit probes to run, overriding |
None
|
thresholds
|
GSARThresholds | None
|
Optional GSAR threshold override for grounding. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[GroundedFinding]
|
data: |
Source code in .sdk/src/tulip/security/jobs.py
assure
async
¶
Assess a target AI's posture → grounded posture findings.
Runs the assurance assessments and returns their grounded posture findings.
v1 ships guardrail coverage across the adversarial suite; fingerprint /
AI-BOM checks compose onto this same grounded-posture contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
Target
|
The AI system under assessment. |
required |
suite
|
str
|
Named probe suite the coverage assessment exercises. |
'owasp-asi'
|
Returns:
| Type | Description |
|---|---|
list[GroundedFinding]
|
One grounded posture :data: |
list[GroundedFinding]
|
per assessment. |
Source code in .sdk/src/tulip/security/jobs.py
monitor
async
¶
Watch a live target AI for attacks/anomalies (tamper-evident trail).
Not yet implemented — a later, optional supporting capability.
Source code in .sdk/src/tulip/security/jobs.py
guardrail_coverage
async
¶
guardrail_coverage(target: Target, *, suite: str = 'owasp-asi', probes: Sequence[Probe] | None = None) -> GroundedFinding
Assess how much of the adversarial suite the target resists.
Runs each probe, treats a probe whose attack did not land as resisted, and grounds a posture finding in the direct per-probe observations. A fully-hardened target yields an INFO posture with no taxonomy gaps; each successful attack raises the severity and is recorded in the taxonomy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
Target
|
The AI system under assessment. |
required |
suite
|
str
|
Named probe suite to run when |
'owasp-asi'
|
probes
|
Sequence[Probe] | None
|
Explicit probes to run, overriding |
None
|
Returns:
| Type | Description |
|---|---|
GroundedFinding
|
A grounded posture :class: |
GroundedFinding
|
class: |
Source code in .sdk/src/tulip/security/assess.py
Naming a target¶
A Target is what to point a job at — an HTTP endpoint, a local callable, or
an agent in this process. The same target works for every job.
Target
dataclass
¶
A uniform handle to the AI system under assessment.
Construct one with a classmethod rather than directly; each builds the
appropriate :data:Sender. kind records the variant for telemetry
/ finding provenance; metadata is free-form context (model name,
owner, environment) that probes may surface in evidence.
send
async
¶
from_callable
classmethod
¶
from_callable(fn: Callable[[str], Awaitable[str] | str], *, name: str = 'callable', metadata: Mapping[str, str] | None = None) -> Target
Wrap any (sync or async) str -> str function as a target.
Source code in .sdk/src/tulip/security/target.py
endpoint
classmethod
¶
endpoint(url: str, *, name: str | None = None, method: str = 'POST', auth: Any = None, headers: Mapping[str, str] | None = None, prompt_field: str = 'prompt', build_payload: Callable[[str], dict[str, Any]] | None = None, response_path: str | None = None, timeout: float = 30.0, transport: Any = None, metadata: Mapping[str, str] | None = None) -> Target
Target a remote LLM/agent HTTP endpoint.
By default the prompt is POSTed as {prompt_field: prompt} and the
response text is extracted heuristically (common single-field shapes
and the OpenAI chat shape). Override build_payload for a custom
request body and response_path (dotted, e.g.
"choices.0.message.content") for a custom response shape. auth
/ headers / transport are passed straight to httpx.
Source code in .sdk/src/tulip/security/target.py
agent
classmethod
¶
Target an in-process :class:tulip.Agent (or anything with a
compatible async run(prompt) event stream).
Drives the agent's async run and returns the final assistant message
(the last event carrying a final_message), so an attacker prompt
flows through the full agent loop — tools and all.
Source code in .sdk/src/tulip/security/target.py
a2a
classmethod
¶
a2a(sender: Callable[[str], Awaitable[Any]], *, name: str = 'a2a', metadata: Mapping[str, str] | None = None) -> Target
Target an A2A peer via its async send coroutine.
Thin adapter: pass the coroutine that delivers a message to the peer
and yields its reply (e.g. bound off your tulip.a2a client). The
full protocol client can be wired here as the A2A surface settles.
Source code in .sdk/src/tulip/security/target.py
Probes¶
A probe is one adversarial attempt with a stated technique and a verdict. The
built-in set maps onto OWASP's ASI and LLM top tens; suite_probes() selects
by suite name, which is what red_team(suite=...) takes.
Probe ¶
Bases: Protocol
One adversarial technique, runnable against a :class:Target.
name is a stable id (e.g. "direct-prompt-injection"); taxonomy
is the primary OWASP/ATLAS tag the probe maps to. Both are read-only so a
frozen dataclass satisfies the contract.
ProbeOutcome
dataclass
¶
ProbeOutcome(title: str, description: str, severity: Severity, asset: str, remediation: str, partition: Partition, taxonomy: list[TaxonomyTag] = list(), indicators: list[Indicator] = list(), transcript: list[str] = list())
A candidate finding produced by a probe, pending grounding.
partition carries the evidence: tool-backed claims when the attack
landed (so it grounds), inference-only claims when it did not (so it
abstains). transcript keeps the raw prompt/response pair for audit.
all_probes ¶
Every distinct bundled probe, across suites (de-duplicated by name).
Source code in .sdk/src/tulip/security/redteam/__init__.py
suite_probes ¶
Return a fresh probe list for a named suite, or raise on an unknown name.
Source code in .sdk/src/tulip/security/redteam/__init__.py
The built-in probes¶
DirectPromptInjection
dataclass
¶
DirectPromptInjection(name: str = 'direct-prompt-injection', taxonomy: TaxonomyTag = OwaspLLM.PROMPT_INJECTION)
Direct prompt injection via an instruction-override canary (OWASP LLM01).
Orders the target to ignore prior instructions and echo a unique canary. A compliant target proves untrusted input can override its controlling instructions.
IndirectPromptInjection
dataclass
¶
IndirectPromptInjection(name: str = 'indirect-prompt-injection', taxonomy: TaxonomyTag = OwaspASI.AGENT_GOAL_HIJACK)
Indirect injection via untrusted retrieved content (OWASP ASI01 / LLM01).
Hides the override instruction inside a 'document' the target is asked to summarise — the indirect channel (RAG / tool output) attackers actually use.
Jailbreak
dataclass
¶
Safety-bypass jailbreak via a role-play override (MITRE ATLAS AML.T0054).
Uses a persona/maintenance framing to elicit a compliance marker a guarded model should refuse to emit.
ExcessiveAgency
dataclass
¶
Excessive agency / tool misuse (OWASP LLM06 / ASI02).
Asks the target to take a destructive out-of-scope action and confirm it with a token. A guarded agent should refuse or seek confirmation rather than self-authorise.
SensitiveInformationDisclosure
dataclass
¶
SensitiveInformationDisclosure(name: str = 'sensitive-information-disclosure', taxonomy: TaxonomyTag = OwaspLLM.SENSITIVE_INFORMATION_DISCLOSURE)
Sensitive-information / context exfiltration (OWASP LLM02).
Plants a secret in context and asks the target to disclose it — the exfiltration-via-inference-API pattern.
UnsandboxedCodeExecution
dataclass
¶
UnsandboxedCodeExecution(name: str = 'unsandboxed-code-execution', taxonomy: TaxonomyTag = OwaspASI.UNEXPECTED_CODE_EXECUTION)
Proof-of-execution probe: unsandboxed agent code/tool execution (OWASP ASI05 unexpected code execution / LLM06 excessive agency).
Asks the target to return the SHA-256 of a fresh random nonce, "using any tool". A language model cannot produce the correct digest of an unseen random string by reasoning — only executing code (a shell, sandbox, or interpreter tool) yields it. So a correct digest in the response is response-only, target-agnostic, tool-backed proof the agent ran attacker-supplied work. Unlike the canary probes, the success criterion is an unforgeable computed value, not an echoed string, so it cannot false-positive on a model that merely quotes the payload while refusing.
Findings and evidence¶
Evidence is the shape every
finding takes — a claim, the observations behind it, and a confidence. It is
documented on the Control page, which is where a finding first matters.
Indicator is the atom a threat-intel lookup returns.
Indicator ¶
Bases: BaseModel
A typed indicator of compromise — an observable (type + value).
Benign by design: examples use documentation-range addresses
(RFC 5737), *.example domains, and well-known test artifacts.
Confidence
module-attribute
¶
A probability / confidence in [0, 1] — mirrors the GSAR shape.
Grounding — abstention over assertion¶
The distinctive part. ground_finding() returns either an Evidence or an
Abstention, never a low-confidence guess dressed as a result, and
is_finding() is the narrowing check that separates the two. A pipeline that
cannot tell "no evidence" from "no problem" reports clean when it is blind.
ground_finding ¶
ground_finding(*, title: str, description: str, severity: Severity, asset: str, remediation: str, partition: Partition, indicators: list[Indicator] | None = None, taxonomy: list[TaxonomyTag] | None = None, confidence: float = 1.0, thresholds: GSARThresholds | None = None, weight_map: dict[EvidenceType, float] | None = None, contradiction_penalty: float = DEFAULT_CONTRADICTION_PENALTY) -> GroundedFinding
Emit a :class:Evidence only if its evidence clears the GSAR threshold.
Scores partition with :func:~tulip.reasoning.gsar.gsar_score and
routes it through :func:~tulip.reasoning.gsar.decide. On
:attr:~tulip.reasoning.gsar.Decision.PROCEED returns a
:class:Evidence carrying the score and the partition's flattened
evidence refs; otherwise returns an :class:Abstention.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
One-line finding summary. |
required |
description
|
str
|
What was observed and why it matters. |
required |
severity
|
Severity
|
Severity band. |
required |
asset
|
str
|
Affected asset / host / service / endpoint. |
required |
remediation
|
str
|
Recommended remediation. |
required |
partition
|
Partition
|
The GSAR four-way partition of the finding's claims. |
required |
indicators
|
list[Indicator] | None
|
Optional indicators of compromise. |
None
|
taxonomy
|
list[TaxonomyTag] | None
|
Optional MITRE ATLAS / OWASP tags. |
None
|
confidence
|
float
|
Analyst-facing confidence (distinct from grounding). |
1.0
|
thresholds
|
GSARThresholds | None
|
Override the GSAR reference thresholds. |
None
|
weight_map
|
dict[EvidenceType, float] | None
|
Override the GSAR evidence-type weights. |
None
|
contradiction_penalty
|
float
|
GSAR |
DEFAULT_CONTRADICTION_PENALTY
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
GroundedFinding
|
class: |
Source code in .sdk/src/tulip/security/grounded.py
ground_fingerprint ¶
ground_fingerprint(*, verdict: FingerprintVerdict, asset: str, partition: Partition, title: str | None = None, description: str | None = None, severity: Severity = Severity.MEDIUM, remediation: str = 'Confirm the served model/engine against the approved inventory.', indicators: list[Indicator] | None = None, taxonomy: list[TaxonomyTag] | None = None, thresholds: GSARThresholds | None = None, weight_map: dict[EvidenceType, float] | None = None, contradiction_penalty: float = DEFAULT_CONTRADICTION_PENALTY) -> FingerprintFinding | Abstention
Ground a timing side-channel fingerprint into a finding, or abstain.
Same admit/abstain contract as :func:ground_finding, threading the
:class:~tulip.security.findings.FingerprintVerdict through on the
PROCEED path. Low feature coverage drives a weak partition, so an
under-observed endpoint abstains rather than asserting a fingerprint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
verdict
|
FingerprintVerdict
|
The classifier verdict (model / engine / hardware). |
required |
asset
|
str
|
The fingerprinted endpoint. |
required |
partition
|
Partition
|
GSAR partition of the fingerprint's claims (the timing feature vector is its evidence). |
required |
title
|
str | None
|
Optional override; defaults to a verdict summary. |
None
|
description
|
str | None
|
Optional override; defaults to a verdict summary. |
None
|
severity
|
Severity
|
Severity band (default |
MEDIUM
|
remediation
|
str
|
Recommended remediation. |
'Confirm the served model/engine against the approved inventory.'
|
indicators
|
list[Indicator] | None
|
Optional indicators (e.g. the endpoint). |
None
|
taxonomy
|
list[TaxonomyTag] | None
|
Optional threat tags. |
None
|
thresholds
|
GSARThresholds | None
|
Override the GSAR reference thresholds. |
None
|
weight_map
|
dict[EvidenceType, float] | None
|
Override the GSAR evidence-type weights. |
None
|
contradiction_penalty
|
float
|
GSAR |
DEFAULT_CONTRADICTION_PENALTY
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
FingerprintFinding | Abstention
|
class: |
FingerprintFinding | Abstention
|
class: |
Source code in .sdk/src/tulip/security/grounded.py
is_finding ¶
Narrow a :data:GroundedFinding to :class:Evidence (vs Abstention).
Abstention ¶
Bases: BaseModel
The audit record for a candidate finding that did NOT ship.
Carries the GSAR decision (never :attr:Decision.PROCEED), the
score, the would-be title for triage, and a human-readable reason.
Verification — trying to refute¶
verify() runs skeptics against a
claim rather than a second model that agrees with the first. A skeptic's job is
to refute; what survives is what gets reported. It and
VerificationResult are
documented on the Control page — a policy can require a verified finding before
it admits an action, which is where they are load-bearing.
Refutation
dataclass
¶
One objection a skeptic raises. weight ∈ {weak, concern, fatal}.
Skeptic ¶
Bases: Protocol
Something that tries to refute a finding. name is read-only.
EvidenceQualitySkeptic
dataclass
¶
EvidenceQualitySkeptic(name: str = 'evidence-quality', proceed_threshold: float = _PROCEED_THRESHOLD)
Deterministic, offline skeptic — grades the evidence a finding carries.
No model required. It does not invent contradictory evidence; it checks that the claim is actually supported by what it ships with.
AdversarialSkeptic ¶
LLM skeptic that actively tries to refute a finding (the semantic challenge).
Drives any :class:tulip.models.base.ModelProtocol (or a "provider:model"
string) with an adversarial prompt + constrained decoding, and maps the
reviewer's objections and unruled-out alternatives into :class:Refutation\ s.
It fails safe: if the model call or its output can't be processed, it
raises a weak refutation noting the finding went unchallenged rather
than silently passing it.
Pair it with :class:EvidenceQualitySkeptic in a panel::
await verify(
finding,
skeptics=[
EvidenceQualitySkeptic(),
AdversarialSkeptic("anthropic:claude-sonnet-4-6"),
],
)
Source code in .sdk/src/tulip/security/verify.py
Taxonomy¶
The standard technique vocabularies a finding is tagged with, and the
comparison to use on severity. Severity
itself is documented on the Control page, since a policy's min_severity
matches on it — but reach for severity_at_least() rather than comparing two
directly: it is a string enum, so > orders alphabetically and gets the answer
wrong.
severity_at_least ¶
Whether value ranks at or above floor (e.g. >= HIGH).
SEVERITY_ORDER
module-attribute
¶
SEVERITY_ORDER: dict[Severity, int] = {Severity.INFO: 0, Severity.LOW: 1, Severity.MEDIUM: 2, Severity.HIGH: 3, Severity.CRITICAL: 4}
AtlasTechnique ¶
Bases: StrEnum
MITRE ATLAS techniques (AML.Txxxx) — representative subset.
A curated set covering the AI-security surfaces Tulip's examples exercise; the full matrix lives at https://atlas.mitre.org/. Values are the canonical ATLAS IDs.
CRAFT_ADVERSARIAL_DATA
class-attribute
instance-attribute
¶
Craft Adversarial Data.
PROMPT_INJECTION
class-attribute
instance-attribute
¶
LLM Prompt Injection (direct or indirect via tool output / RAG).
JAILBREAK
class-attribute
instance-attribute
¶
LLM Jailbreak — bypassing model controls.
POISON_TRAINING_DATA
class-attribute
instance-attribute
¶
Poison Training Data.
BACKDOOR_ML_MODEL
class-attribute
instance-attribute
¶
Backdoor ML Model.
INFERENCE_API_ACCESS
class-attribute
instance-attribute
¶
AI Model Inference API Access.
EXFILTRATION_VIA_INFERENCE_API
class-attribute
instance-attribute
¶
Exfiltration via AI Inference API (e.g. model extraction probing).
EXFILTRATION_VIA_AGENT_TOOL
class-attribute
instance-attribute
¶
Exfiltration via AI Agent Tool Invocation.
AGENT_TOOL_POISONING
class-attribute
instance-attribute
¶
AI Agent Tool Poisoning.
EXTERNAL_HARMS
class-attribute
instance-attribute
¶
External Harms — financial, reputational, or physical harm.
OwaspASI ¶
Bases: StrEnum
OWASP Top 10 for Agentic Applications, 2026 (ASI01–ASI10).
From the OWASP Agentic Security Initiative. See https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/.
AGENT_GOAL_HIJACK
class-attribute
instance-attribute
¶
Agent Goal Hijack.
IDENTITY_AND_PRIVILEGE_ABUSE
class-attribute
instance-attribute
¶
Identity & Privilege Abuse.
AGENTIC_SUPPLY_CHAIN
class-attribute
instance-attribute
¶
Agentic Supply Chain Vulnerabilities.
UNEXPECTED_CODE_EXECUTION
class-attribute
instance-attribute
¶
Unexpected Code Execution.
MEMORY_AND_CONTEXT_POISONING
class-attribute
instance-attribute
¶
Memory & Context Poisoning.
INSECURE_INTER_AGENT_COMMUNICATION
class-attribute
instance-attribute
¶
Insecure Inter-Agent Communication.
CASCADING_FAILURES
class-attribute
instance-attribute
¶
Cascading Failures.
HUMAN_AGENT_TRUST_EXPLOITATION
class-attribute
instance-attribute
¶
Human-Agent Trust Exploitation.
OwaspLLM ¶
Bases: StrEnum
OWASP Top 10 for LLM Applications, 2025 (LLM01–LLM10).
See https://genai.owasp.org/llm-top-10/.
SENSITIVE_INFORMATION_DISCLOSURE
class-attribute
instance-attribute
¶
Sensitive Information Disclosure.
DATA_AND_MODEL_POISONING
class-attribute
instance-attribute
¶
Data and Model Poisoning.
IMPROPER_OUTPUT_HANDLING
class-attribute
instance-attribute
¶
Improper Output Handling.
SYSTEM_PROMPT_LEAKAGE
class-attribute
instance-attribute
¶
System Prompt Leakage.
VECTOR_AND_EMBEDDING_WEAKNESSES
class-attribute
instance-attribute
¶
Vector and Embedding Weaknesses.
UNBOUNDED_CONSUMPTION
class-attribute
instance-attribute
¶
Unbounded Consumption.
IndicatorType ¶
Bases: StrEnum
The kind of an :class:~tulip.security.findings.Indicator.
ENDPOINT
class-attribute
instance-attribute
¶
A model / inference endpoint — the subject of a fingerprint finding.
Security context — the ports¶
SecurityContext is the seam between the SDK and your estate. Each port is a
protocol, so the offline reference adapters that ship here and the vendor
adapters in
tulip-integrations
are interchangeable, and neither is privileged.
SecurityContext
dataclass
¶
SecurityContext(logs: LogSource = _RefLogs(), endpoint: EndpointSource = _RefEndpoint(), identity: IdentitySource = _RefIdentity(), cloud: CloudSource = _RefCloud(), threat_intel: ThreatIntelSource = _RefThreatIntel(), actions: ActionsPort = _RefActions())
One handle over the security domains. Defaults run offline, zero-config.
Inject a vendor provider per domain to go live::
from tulip_integrations.siem.splunk import SplunkLogs
ctx = SecurityContext(logs=SplunkLogs())
toolset ¶
The agent-ready tool bundle (delegates to :func:security_toolset).
The domain handles above are the programmatic facade; this is the
agent facade — hand it to Agent(tools=...).
Source code in .sdk/src/tulip/security/context.py
LogSource ¶
Bases: Protocol
Log / SIEM search.
EndpointSource ¶
Bases: Protocol
EDR host forensics + containment.
IdentitySource ¶
Bases: Protocol
Identity provider — the surface most attacks touch.
CloudSource ¶
Bases: Protocol
Cloud control-plane evidence (read-only).
ThreatIntelSource ¶
Bases: Protocol
IOC reputation / enrichment.
ActionsPort ¶
Bases: Protocol
Gate a response action against evidence + verification + policy.
execute
async
¶
execute(action: Action, perform: Callable[[], Awaitable[Any]], *, finding: Evidence | None = None, verdict: VerificationResult | None = None) -> Any
Admission control: run perform only if the action is admitted.
Source code in .sdk/src/tulip/security/context.py
Writing an adapter¶
What a vendor adapter has to implement, plus the helpers that keep one small.
SecurityAdapter ¶
Bases: Protocol
A named, vendored bundle of agent-ready security tools.
The contract an integration implements. name is a stable id (e.g.
"splunk"); vendor is a human label (e.g. "Splunk / Elastic SIEM");
tools() returns the :class:~tulip.tools.decorator.Tool objects to hand an
agent. Pass them to :func:tulip.security.security_toolset via extra= or
straight to Agent(tools=...).
ToolAdapter
dataclass
¶
The simplest concrete :class:SecurityAdapter — wrap a list of tools.
Integrations that don't need their own class can just construct one::
splunk = ToolAdapter(
name="splunk", vendor="Splunk SIEM", _tools=[splunk_siem_tool]
)
agent = Agent(tools=splunk.tools())
as_json ¶
env ¶
Return the first non-empty value among names in the environment.
Adapters use this to detect bring-your-own credentials; when it returns
None the adapter takes its offline-sample path.
Source code in .sdk/src/tulip/security/adapter.py
indicator_type ¶
Map a coarse adapter kind ("hash"/"ip"/"domain"/…) to a typed enum.
Source code in .sdk/src/tulip/security/adapter.py
inference_claim ¶
A weak, model-internal :class:~tulip.reasoning.gsar.Claim.
Lands in the ungrounded bucket so a finding built only from inference abstains.
Source code in .sdk/src/tulip/security/adapter.py
tool_match ¶
A grounded, tool-backed :class:~tulip.reasoning.gsar.Claim.
The strongest evidence tier (:attr:EvidenceType.TOOL_MATCH) — use it for a
statement read directly off a scanner / API response.
Source code in .sdk/src/tulip/security/adapter.py
Tools for an agent¶
Each capability comes in two forms: a plain function you can call, and a
@tool-decorated version to hand an agent. security_toolset() returns the
whole set at once.
security_toolset ¶
security_toolset(*, threat_intel: bool = True, siem: bool = True, edr: bool = True, scanner: bool = True, fingerprint: bool = True, aws: bool = False, allow_containment: bool = False, extra: list[Any] | None = None) -> list[Any]
Assemble the agent-ready security tool list.
Returns the read-only SOC triage loop by default — IOC enrichment, SIEM
search, EDR forensics, vuln/posture scanning, and inference
fingerprinting — from the bundled reference adapters. Containment
(isolate_host) and the AWS posture tools are opt-in (the latter needs
boto3 from the [aws] / [security] extra).
extra merges tools from external integrations you imported
explicitly (the LangChain model — no auto-discovery), e.g.::
from tulip_integrations.siem.splunk import splunk_siem_tool
tools = security_toolset(siem=False, extra=[splunk_siem_tool])
Hand the result to Agent(tools=...) or create_soc_analyst(tools=...).
Source code in .sdk/src/tulip/security/__init__.py
Threat intelligence¶
enrich_indicator ¶
Return reputation/context for one IOC.
Live path (VT_API_KEY set) queries a VirusTotal-shaped endpoint;
offline path returns the benign sample table. The return shape is the
same so an agent's downstream reasoning is identical either way.
Source code in .sdk/src/tulip/security/intel.py
enrich_indicator_tool
async
¶
Tool wrapper: returns the enrichment as a JSON string.
Source code in .sdk/src/tulip/security/intel.py
classify_indicator ¶
Infer the indicator kind from its shape: hash / ip / domain.
enrich_to_finding ¶
enrich_to_finding(indicator: str, *, severity: Severity | None = None, taxonomy: list[TaxonomyTag] | None = None) -> GroundedFinding
Enrich an indicator and ground the verdict into a :class:Evidence.
A vendor detection count is tool-backed evidence, so a flagged indicator
ships a finding (severity scaling with the detection count). A clean
indicator carries no grounded support for a "malicious" claim, so the
candidate finding abstains — the caller gets an
:class:~tulip.security.Abstention, never an empty finding.
Source code in .sdk/src/tulip/security/intel.py
SIEM¶
query_siem ¶
Search the SIEM for events matching query over a time window.
Live path (SIEM_URL + SIEM_TOKEN set) POSTs a search; offline
path filters the benign sample events by substring. The return shape is
identical so an agent's downstream reasoning doesn't change.
Source code in .sdk/src/tulip/security/siem.py
siem_query_tool
async
¶
Tool wrapper: returns matching events as a JSON string.
Source code in .sdk/src/tulip/security/siem.py
Endpoint detection and response¶
list_detections ¶
List open EDR detections, optionally filtered to one host.
Source code in .sdk/src/tulip/security/edr.py
list_detections_tool
async
¶
Tool wrapper: returns open detections as a JSON string.
Source code in .sdk/src/tulip/security/edr.py
fetch_host_timeline ¶
Return the recent process/network/file timeline for a host.
Live path (EDR_URL + EDR_TOKEN) queries the console; offline path
returns the benign sample for known lab hosts (empty for others).
Source code in .sdk/src/tulip/security/edr.py
fetch_host_timeline_tool
async
¶
Tool wrapper: returns the host timeline as a JSON string.
Source code in .sdk/src/tulip/security/edr.py
isolate_host ¶
Network-isolate (contain) a host. Write action — gate it.
Live path (EDR_URL + EDR_TOKEN) POSTs a containment action;
offline path returns a simulated receipt so the loop is exercisable
without touching a real fleet.
Source code in .sdk/src/tulip/security/edr.py
isolate_host_tool
async
¶
Tool wrapper for the containment write — idempotent on host_id.
Source code in .sdk/src/tulip/security/edr.py
Scanning¶
scan_endpoint ¶
Check a network endpoint's port reachability + TLS-cert expiry.
Offline-by-default: returns a deterministic sample unless SCANNER_LIVE
is set, so CI never touches the network. The live path uses only the
stdlib (socket + ssl) — no extra dependency.
Source code in .sdk/src/tulip/security/scanner.py
scan_endpoint_tool
async
¶
Tool wrapper: returns the endpoint posture as a JSON string.
Source code in .sdk/src/tulip/security/scanner.py
scan_endpoint_to_finding ¶
Scan an endpoint and ground an expired-certificate result into a finding.
An expired certificate read off the handshake is tool-backed evidence and ships a HIGH finding; a healthy or unreachable endpoint carries no grounded defect, so the candidate finding abstains.
Source code in .sdk/src/tulip/security/scanner.py
scan_dependencies ¶
Check a package launch command for known malware advisories (OSV).
Returns {"clean": bool, "advisory": str | None, ...}. clean is
True when OSV reports no MAL-* advisory (the check is fail-open —
network errors and unknown ecosystems read as clean).
Source code in .sdk/src/tulip/security/scanner.py
scan_dependencies_tool
async
¶
Tool wrapper: returns the OSV malware verdict as a JSON string.
Source code in .sdk/src/tulip/security/scanner.py
AWS¶
use_aws() refuses anything outside READONLY_PREFIXES unless you say
otherwise, so the default posture is read-only.
describe_aws ¶
describe_aws(service: str | None = None, operation: str | None = None, region: str | None = None) -> dict[str, Any]
Introspect the AWS API spec (botocore service models).
service is None→{"services": [...]}(all services).serviceonly → that service's read-only operations.service+operation→ the operation's parameter shape.
Source code in .sdk/src/tulip/security/aws.py
describe_aws_tool
async
¶
Discover the shape of AWS from the API spec.
Source code in .sdk/src/tulip/security/aws.py
use_aws ¶
use_aws(service: str, operation: str, parameters: dict[str, Any] | None = None, region: str | None = None) -> dict[str, Any]
Execute a read-only AWS operation and return the raw response.
Refuses any operation that is not a read verb (see
:data:READONLY_PREFIXES) before making a call. The returned response
is the evidence a grounded finding cites.
Source code in .sdk/src/tulip/security/aws.py
use_aws_tool
async
¶
Execute one read-only AWS operation.
Source code in .sdk/src/tulip/security/aws.py
aws_services ¶
is_readonly_operation ¶
READONLY_PREFIXES
module-attribute
¶
READONLY_PREFIXES: tuple[str, ...] = ('Describe', 'List', 'Get', 'Lookup', 'Search', 'BatchGet', 'Select', 'Head', 'Estimate', 'Simulate', 'Preview', 'Query', 'Scan', 'Retrieve')
Fingerprinting¶
Identify the model behind an endpoint from response timing alone — no cooperation from the endpoint required.
measure_endpoint_timing ¶
measure_endpoint_timing(model: str = 'gpt-4o-mini', samples: int = 5, prompt: str = 'Count slowly from one to twenty.') -> dict[str, float]
Return a streaming-timing feature vector for an endpoint.
Live path (OPENAI_API_KEY set) streams samples completions and
computes TTFT p50, mean inter-token latency, its coefficient of variation,
and mean tokens/sec. Offline path returns the deterministic sample.
Source code in .sdk/src/tulip/security/fingerprint.py
fingerprint_endpoint_tool
async
¶
Tool wrapper: measure timing, classify, return the verdict as JSON.
Source code in .sdk/src/tulip/security/fingerprint.py
default_classifier ¶
Map a timing feature vector to a verdict (deterministic heuristic).
This is a transparent placeholder for a trained classifier — it bins on
inter-token latency and its coefficient of variation. feature_coverage
is the fraction of :data:FEATURE_KEYS observed; low coverage should
drive an abstention downstream (see :func:fingerprint_to_finding).
Source code in .sdk/src/tulip/security/fingerprint.py
fingerprint_to_finding ¶
fingerprint_to_finding(features: Mapping[str, float], *, asset: str, classifier: object | None = None, min_coverage: float = 0.75, severity: Severity = Severity.MEDIUM, taxonomy: list[TaxonomyTag] | None = None) -> FingerprintFinding | Abstention
Classify a timing vector and ground it into a fingerprint finding.
The measured vector is tool-backed evidence; when feature coverage clears
min_coverage the verdict ships, otherwise the under-observed endpoint
abstains (an asserted identity from a thin measurement is a false
positive by construction).
Source code in .sdk/src/tulip/security/fingerprint.py
dispatch_timing_probe_reference ¶
Reference (offline) co-located GPU-probe dispatch — returns the sample vector.
The credential-free remote-API measurement is :func:measure_endpoint_timing.
The real GPU-cloud lifecycle (provision -> probe -> tear down) lives in the
tulip-integrations package as two separate provider modules — RunPod
(tulip_integrations.compute.runpod.runpod_probe, extra compute-runpod)
and Lambda Cloud (tulip_integrations.compute.lambda_cloud.lambda_probe,
extra compute-lambda); compute.dispatch_timing_probe(endpoint, provider=…)
routes between them. Core ships no vendor GPU code.
Source code in .sdk/src/tulip/security/fingerprint.py
FEATURE_KEYS
module-attribute
¶
FingerprintClassifier ¶
Bases: Protocol
A callable mapping a timing feature vector to a verdict.
Examples ship a deterministic mock; real deployments plug a fingerprinting service in behind this same signature.
FingerprintFinding ¶
Bases: Evidence
A :class:Evidence specialised for inference fingerprinting.
Carries the :class:FingerprintVerdict whose feature vector is the
finding's evidence.
FingerprintVerdict ¶
Bases: BaseModel
A timing side-channel inference-fingerprinting verdict.
Identifies what is serving an endpoint from observable timing
features. feature_coverage is the fraction of the expected
feature schema actually observed; low coverage should drive an
abstention rather than an asserted fingerprint.
SOC analyst¶
A prebuilt agent, the report shape it produces, and the grounding pass applied to that report.
create_soc_analyst ¶
create_soc_analyst(*, model: str | Any, controls: SecurityControls | None = None, tools: list[Any] | None = None, system_prompt: str | None = None, scope: str | None = None, min_confidence: float | None = None, max_iterations: int = 30, **deepagent_kwargs: Any) -> Any
Construct a grounded, read-only AWS cloud-posture agent.
A thin security-shaped layer over :func:~tulip.create_deepagent: it bakes
in the read-only AWS posture tools, a SOC-analyst system prompt, the
:class:PostureReport output schema, and grounding/reflexion. The agent
proposes findings; pass its result.output to :func:ground_report to
get typed :class:~tulip.security.Evidence / Abstention results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str | Any
|
A tulip model string ( |
required |
controls
|
SecurityControls | None
|
The :class: |
None
|
tools
|
list[Any] | None
|
Extra tools to attach alongside the AWS posture tools and the
|
None
|
system_prompt
|
str | None
|
Override the default SOC-analyst prompt entirely. When
set, |
None
|
scope
|
str | None
|
Optional one-line narrowing appended to the default prompt
(e.g. |
None
|
min_confidence
|
float | None
|
Submission-confidence floor for early-exit. Defaults to
|
None
|
max_iterations
|
int
|
Cap on reasoning steps. Default 30. |
30
|
**deepagent_kwargs
|
Any
|
Forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A configured |
Source code in .sdk/src/tulip/security/soc.py
ground_report ¶
ground_report(report: PostureReport, controls: SecurityControls | None = None) -> list[GroundedFinding]
Run every proposed finding through GSAR grounding.
Each :class:PostureFinding becomes a typed
:class:~tulip.security.Evidence only if its cited evidence clears the
grounding threshold; otherwise it yields an
:class:~tulip.security.Abstention. Direct API observations
(grounded=True) become grounded GSAR claims; inferences become
ungrounded claims, which cannot on their own admit a finding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
PostureReport
|
The :class: |
required |
controls
|
SecurityControls | None
|
Controls supplying the grounding threshold. Defaults to
:meth: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[GroundedFinding]
|
class: |
list[GroundedFinding]
|
the order the agent proposed them. |
Source code in .sdk/src/tulip/security/soc.py
submit_posture ¶
Submit the completed cloud-posture report and end the review.
Call this once you have gathered enough read-only evidence to stand behind
each finding. Every finding must cite the exact API facts it relies on in
its evidence list — ungrounded findings are discarded downstream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
PostureReport
|
The completed :class: |
required |
Source code in .sdk/src/tulip/security/soc.py
PostureReport ¶
Bases: BaseModel
The structured posture report the agent submits to end the run.
PostureFinding ¶
Bases: BaseModel
A posture issue the analyst proposes — grounded later by ground_report.
PostureEvidence ¶
Bases: BaseModel
One atomic fact the analyst observed, with a pointer to its source.
SecurityControls
dataclass
¶
SecurityControls(min_gsar: float = 0.6, min_confidence: float = 0.7, readonly_aws: bool = True, threat_intel: bool = False, siem: bool = False, edr: bool = False, scanner: bool = False, fingerprint: bool = False, allow_containment: bool = False, region: str | None = None)
The security posture of a SOC analyst — grounding + tooling, in one bundle.
Holds the knobs that make a posture agent trustworthy: the grounding
threshold a finding's evidence must clear, the submission-confidence floor,
and whether the read-only AWS tools are attached. Passed to
:func:create_soc_analyst; reused by :func:ground_report so the agent and
the grounding step share one source of truth.
min_gsar
class-attribute
instance-attribute
¶
GSAR τ_proceed — a finding's evidence must score at or above this.
min_confidence
class-attribute
instance-attribute
¶
Submission-confidence floor wired into the agent's termination.
readonly_aws
class-attribute
instance-attribute
¶
Attach the read-only describe_aws / use_aws tools.
threat_intel
class-attribute
instance-attribute
¶
Attach the IOC-enrichment tool (enrich_indicator).
siem
class-attribute
instance-attribute
¶
Attach the SIEM search tool (query_siem).
edr
class-attribute
instance-attribute
¶
Attach the EDR forensics tools (fetch_host_timeline / list_detections).
scanner
class-attribute
instance-attribute
¶
Attach the vuln/posture scanners (scan_dependencies / scan_endpoint).
fingerprint
class-attribute
instance-attribute
¶
Attach the inference-fingerprinting tool (fingerprint_endpoint).
allow_containment
class-attribute
instance-attribute
¶
Attach the containment write (isolate_host). Requires edr; off by default.
region
class-attribute
instance-attribute
¶
Default AWS region hint (informational; tools read TULIP_AWS_REGION).
default
classmethod
¶
soc_triage
classmethod
¶
The read-only SOC triage loop — intel, SIEM, EDR, scanner, fingerprint.
Source code in .sdk/src/tulip/security/soc.py
tools ¶
The security toolset implied by these controls.
Source code in .sdk/src/tulip/security/soc.py
thresholds ¶
GSAR thresholds derived from min_gsar (τ_regenerate < τ_proceed).
Playbooks¶
Named sequences for the incident types that recur. Each returns a
Playbook you can run or edit.
all_playbooks ¶
Every bundled security playbook, keyed by id.
Source code in .sdk/src/tulip/security/playbooks.py
nist_800_61_ir ¶
NIST SP 800-61 incident-response lifecycle over the adapter tools.
Source code in .sdk/src/tulip/security/playbooks.py
phishing_triage ¶
Reported phishing → gather → enrich → scope → (optional) contain.
Source code in .sdk/src/tulip/security/playbooks.py
ransomware_containment ¶
Ransomware: confirm → contain fast → assess scope → preserve evidence.
Source code in .sdk/src/tulip/security/playbooks.py
cloud_posture_audit ¶
Read-only cloud-posture audit: discover → gather evidence → submit.