Agentic AI red-teaming¶
The flagship of Tulip's security layer: point a Target at an AI system
and run the OWASP-ASI / MITRE-ATLAS red-team suite. Every result is a grounded
Evidence (the attack landed, with tool-backed evidence) or an Abstention
(no evidence — so nothing is asserted).
That abstain-by-construction property is the line no other red-team tool draws:
AI scorers hallucinate vulnerabilities; Tulip refuses to ship one it cannot
evidence. The notebook points the same suite at two targets — a vulnerable bot
that obeys injected instructions (→ grounded Evidence) and a hardened one that
refuses them (→ Abstentions).
Runs fully offline via Target.from_callable. Point
Target.endpoint(url, ...) at a real LLM / agent endpoint to red-team it for
real.
Run it: python examples/notebook_75_agent_red_team.py
Source¶
#!/usr/bin/env python3
# Copyright 2026 Tulip Labs
# SPDX-License-Identifier: Apache-2.0
"""Notebook 75: agentic AI red-teaming — grounded findings or abstentions.
The flagship of the agentic-AI-security SDK: point a ``Target`` at an AI
system and run the OWASP-ASI / MITRE-ATLAS red-team suite. Every result is a
grounded ``Evidence`` (the attack landed, with tool-backed evidence) or an
``Abstention`` (no evidence — so nothing is asserted). That abstain-by-
construction property is the line no other red-team tool draws: AI scorers
hallucinate vulnerabilities; Tulip refuses to ship one it cannot evidence.
This notebook points the same suite at two targets:
1. a *vulnerable* bot that obeys injected instructions -> grounded Findings
2. a *hardened* bot that refuses them -> Abstentions
Runs fully offline — no credentials, no network — via ``Target.from_callable``.
Point ``Target.endpoint(url, ...)`` at a real LLM/agent endpoint to red-team it
for real.
Run:
python examples/notebook_75_agent_red_team.py
"""
from __future__ import annotations
import asyncio
from tulip.security import Target, is_finding, red_team
def _vulnerable_bot(prompt: str) -> str:
"""A naive assistant with no input/output separation: it parrots back
whatever it is told, so injected instructions (and their canaries) flow
straight through."""
return prompt
def _hardened_bot(_prompt: str) -> str:
"""A guarded assistant that refuses to follow untrusted instructions."""
return "I can't comply with that. I follow only my approved instructions."
async def _assess(target: Target) -> None:
results = await red_team(target, suite="owasp-asi")
findings = [r for r in results if is_finding(r)]
abstentions = [r for r in results if not is_finding(r)]
print(f"\n== Red-team report for {target.name!r} ({len(results)} probes) ==")
print(f" findings: {len(findings)} abstentions: {len(abstentions)}")
for r in results:
if is_finding(r):
tags = ", ".join(str(t) for t in r.taxonomy)
print(f" [FINDING ] {r.severity.value:<8} {tags:<14} {r.title}")
print(f" grounded @ {r.gsar_score:.2f} · evidence: {r.evidence_refs}")
else:
print(f" [ABSTAIN ] {r.candidate_title}")
print(f" {r.reason}")
async def main() -> int:
print("Agentic AI red-team — grounded findings or abstentions (offline demo)")
vulnerable = Target.from_callable(_vulnerable_bot, name="vulnerable-bot")
hardened = Target.from_callable(_hardened_bot, name="hardened-bot")
await _assess(vulnerable)
await _assess(hardened)
print(
"\nThe vulnerable bot produced grounded Findings; the hardened bot abstained "
"across the board. No vulnerability is ever asserted without evidence."
)
return 0
if __name__ == "__main__":
raise SystemExit(asyncio.run(main()))