Back to home
🚧 Tripwire
Red-teaming framework that stress-tests LLM guardrails against the OWASP Top 10
Tripwire is an open-source security framework that systematically attacks LLM application guardrails and produces an actionable scorecard mapped to the OWASP LLM Top 10 (2025).
Most teams deploying LLM-powered apps — chatbots, copilots, RAG systems — rely on manual spot-checking or expensive pentests to validate their defenses. Tripwire automates that process: point it at any REST endpoint, and it fires 250+ adversarial payloads across all 10 OWASP categories, analyzes responses through a 3-layer detection pipeline (heuristic → structural → LLM-as-judge), and outputs a weighted 0–100 security score with per-category breakdowns.
What makes it different from tools like NVIDIA Garak or Microsoft PyRIT is the focus on application-layer guardrails — the middleware (NeMo Guardrails, Llama Guard, custom filters) sitting between users and the model — rather than probing the raw LLM itself. It also supports adaptive attack generation: after the static corpus runs, Tripwire feeds the results to Claude to generate novel payloads that exploit patterns in what the target blocked vs. what it let through.
The project ships as a CLI tool (tw scan), a FastAPI REST API for CI/CD integration, and a React dashboard with real-time scan monitoring, interactive scorecards, and drill-down views per vulnerability category.

🛠️ Tech Stack
🚀 Features
Full OWASP LLM Top 10 coverage — 250 curated attack payloads across all 10 categories including prompt injection, system prompt leakage, sensitive disclosure, excessive agency, and misinformation
3-layer detection pipeline with short-circuit optimization — 13 category-specific heuristic detectors (Layer 1), structural analyzers for response length / encoding / output format anomalies (Layer 2), and an LLM-as-judge using Claude for ambiguous cases (Layer 3)
Adaptive attack generation — two-pass scan engine where Pass 2 feeds blocked-vs-bypassed results to Claude via Jinja2 mutation prompts, generating novel payloads that discover vulnerabilities static corpora miss
Weighted scoring system — 0–100 score with 2x weighting on high-impact categories (prompt injection, sensitive disclosure, system prompt leakage) and per-category PASS/WARN/FAIL status based on severity distribution
Async concurrent scanning —
asyncio.gather()with semaphore-bounded concurrency (configurable 1–20) for high throughput without overwhelming the targetReact scorecard dashboard — circular score ring, category grid cards with pass-rate bars, drill-down detail views with severity pie charts and expandable result tables, plus scan history with multi-scan comparison
CI/CD-ready — exit codes (0 = pass, 1 = fail, 2 = error),
--quietmode, JSON export, configurable severity thresholds, and YAML config file support for pipeline integrationFault-tolerant execution — two layers of error isolation (per-category + per-payload) ensure partial results are always produced; connection failures, malformed responses, and detector errors never abort the scan
🧗♂️ Challenges
Designing detection heuristics with acceptable false-positive rates — the
BehavioralDetectorneeded to distinguish genuine jailbreak compliance from conversational language that sounds compliant. Solved with a two-part refusal-vs-compliance heuristic: compliance patterns ("I'm DAN", "no restrictions") are only flagged when refusal patterns ("I can't", "against my rules") are absent. TheSystemPromptLeakDetectorrequires 2+ indicator matches from an 8-pattern set to avoid flagging normal phrases like "You are a great person." This tension between sensitivity and specificity is the core challenge of automated security detection, and tuning these thresholds was one of the most iterative parts of the project.Building an adaptive feedback loop without runaway costs — the two-pass architecture feeds real scan results to Claude to generate targeted follow-up payloads, but LLM API calls are expensive. Bounded this with a configurable
adaptive_budget(default: 10 payloads per category) and graceful degradation — if no API key is set, adaptive mode silently falls back to static-only scanning with a logged warning. The Jinja2 mutation prompt template had to be carefully engineered to produce valid, parseable JSON payloads while encouraging creative attack variations.Async concurrency control across a heterogeneous pipeline — each payload goes through HTTP request → multi-layer detection → optional LLM judge call, with different latency profiles at each stage. Used
asyncio.Semaphoreto gate the HTTP fan-out while keeping detection and scoring synchronous within each task. The per-payload error boundary (try/except wrapping each coroutine) was critical — without it, a single timeout or malformed response would cancel the entiregather()batch.JSON-safe payload injection — attack payloads routinely contain quotes, backslashes, newlines, and unicode escape sequences that break naive string interpolation into JSON request bodies. Solved with
json.dumps(payload)[1:-1]to get properly escaped strings, which was a non-obvious trick that prevents a whole class of silent test failures where the payload never reaches the target intact.
🧠 Learnings
Layered detection mirrors real security architecture. The 3-layer pipeline (fast heuristics → structural checks → expensive LLM judge) with short-circuit evaluation is the same pattern used in production WAFs and antivirus engines. Building it from scratch gave me a deep appreciation for why these systems are layered: each tier trades off cost, speed, and accuracy, and the short-circuit means you only pay for expensive analysis on genuinely ambiguous cases.
Adversarial thinking requires a different design mindset. Writing 250 attack payloads forced me to think like an attacker — encoding bypasses, persona injection, indirect prompt injection via RAG context, role-play escalation. This is a fundamentally different skill from defensive coding and gave me practical insight into why guardrails fail: most are built for expected inputs, not for inputs specifically crafted to find blind spots.
Pydantic v2 models as the single source of truth. Every piece of data — from YAML corpus definitions to scan results to API responses — flows through typed Pydantic models. This eliminated an entire class of bugs where fields were misspelled, missing, or wrong-typed across module boundaries. The
Severityenum with custom ordering (__lt__via a lookup dict) was a small decision that simplified the scoring logic everywhere downstream.Async Python requires disciplined error boundaries. The biggest lesson from the concurrent test runner was that
asyncio.gather()will cancel sibling tasks on any unhandled exception. Wrapping each payload coroutine in its own try/except and returning a "failed"AttackResultinstead of raising was the key insight — it converts crashes into data, which the scorer handles naturally.